最近关于socket编程的理解

  最近看了看socket网络编程,对于我这种一点经验都没有的选手来说只能理解一点点吧。所以在此记录一下最近的收获。

  socket编程无非就那几个函数,对于服务端来说,主要的为socket(),bind(),listen(),accept(),close()。对于客户端来说主要为connect(),close()等。当然,我所说的只是针对tcp协议而言的。对于udp而言,就可以简单很多,服务端和客户端都建立socket并进行绑定,从而用sendto()和recvfrom()通信即可。

  以下直接上一个关于tcp协议的客户端和服务端的程序。

 

//此为服务端的程序#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <netdb.h>
//当SIGCHLD信号出现时则执行此函数
//当子进程停止时,SIGCHLD信号会送给父进程,默认是忽略该信号
void sig_handler(int signo) {
    pid_t pid;
    int stat;
//用WNOHANG参数如果没有任何已终止的进程,它仍会立即返回,而不是像wait那样永远等下去
//waitpid如果成功的话返回子进程的pid,如果没有子进程退出则返回0
    pid = waitpid(-1, &stat, WNOHANG);
    while(pid > 0) {
        printf("child process terminated (PID: %ld)\n", (long)getpid());
        pid = waitpid(-1, &stat, WNOHANG);
    }

    return 0;
}

int main(int argc, char *argv[]) {
    int listen_fd;
    int com_fd;
    int ret;
    int i;
    static char recv_buffer[1024];
    int len;
    int port;
    pid_t pid;
/*
struct sockaddr {
    unsigned short sa_family;   //address family, AF_xxx
    char            sa_data[14]; //14 bytes of protocal address
};
struct sockaddr_in {
    short int            sin_family;  //address family, AF_INET
    unsigned short int sin_port;    //port number
    struct in_addr       sin_addr;    //internet address
    unsigned char      sin_zero[8]; //same sizeas struct sockaddr
};
struct in_addr {
    uint32_t s_addr;   //4 bytes
};
sockaddr是通用套接字地址,sockaddr_in是internet环境下套接字的地址形式,两者
结构一样,都为16个字节。
*/

    struct sockaddr_in clt_addr;
    struct sockaddr_in srv_addr;
//服务器运行时要给出端口信息,该端口为监听端口
    if(argc != 2) {
        printf("Usage: %s port\n", argv[0]);
        return 1;
    }
//atoi为ascii to integer,字符串转换为整型
    port = atoi(argv[1]);
//设置处理信号函数
    if(signal(SIGCHLD, sig_handler) < 0) {
        perror("cannot set the signal");
        return 1;
    }
//创建套接字用于服务器的监听
    if((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("cannot creat listening socket");
        return 1;
    }
//将srv_addr全部置零,INADDR_ANY就是inet_addr("0.0.0.0"),表示不确定
//地址,也就是表示本机的所有IP
    memset(&srv_addr, 0, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    srv_addr.sin_port = htons(port);

    if((ret = bind(listen_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))) == -1) {
        perror("cannot bind server socket");
        close(listen_fd);
        return 1;
    }
//指定监听端口,连接5个客户端
    if((ret = listen(listen_fd, 5)) == -1) {
        perror("cannot listen the client connect request");
        close(listen_fd);
        return 1;
    }
//对每个连接来的客户端创建一个进程,单独与其进行通信
//首先调用read函数读取客户端发送来的信息
//将其转换成为大写后发送回客户端
//当输入@时,程序退出
//用fork()函数的原因是保持服务器能容许多个客户端的连接
    while(1) {
        len = sizeof(clt_addr);
        if((com_fd = accept(listen_fd, (struct sockaddr*)&clt_addr, &len)) < 0) {
//EINTR表示系统调用被信号中断
            if(errno == EINTR) {
                continue;
            }
            else {
                perror("cannot accept client connect request");
                close(listen_fd);
                return 1;
            }
        }

        pid = fork();
        if(pid < 0) {
            perror("cannot create the child process");
            close(listen_fd);
            return 1;
        }
//当有客户端连接的时候,在子进程中进行客户端与服务端的通信
        else if(pid == 0) {
            while((len = read(com_fd, recv_buffer, 1024)) > 0) {
                printf("Message from client(%d): %s\n", len, recv_buffer);
                if(recv_buffer[0] == ‘@‘) {
                    break;
                }
                for(i = 0; i < len; i++) {
                    recv_buffer[i] = toupper(recv_buffer[i]);
                }
                write(com_fd, recv_buffer,len);
                memset(recv_buffer, 0, 1024);
            }

            close(com_fd);
            return 0;
        }
//父进程直接结束此次循环等待下一个客户端进行连接
        else {
            close(com_fd);
        }
    }

    return 0;
}
//此为服务端的程序#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int connect_fd;
    int ret;
    char snd_buffer[1024];
    int i;
    int port;
    int len;

    static struct sockaddr_in srv_addr;

    if(argc != 3) {
        printf("Usage: %s server_ip_address port\n", argv[1]);
        return 1;
    }

    port = atoi(argv[2]);

    if((connect_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("cannot creat communication socket");
        return 1;
    }

    memset(&srv_addr, 0, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    srv_addr.sin_port = htons(port);

    if((ret = connect(connect_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))) == -1) {
        perror("cannot connect to the server");
        close(connect_fd);
        return 1;
    }

    //memset(snd_buffer, 0, 1024);

    while(1) {
        memset(snd_buffer, 0, 1024);
        write(STDOUT_FILENO, "input message: ", 15);
        len = read(STDIN_FILENO, snd_buffer, 1024);
        if(len > 0)
            write(connect_fd, snd_buffer, len);
        len = read(connect_fd, snd_buffer, len);
        if(len > 0)
            printf("Message from serverL %s\n", snd_buffer);
        if(snd_buffer[0] == ‘@‘)
            break;
    }

    close(connect_fd);
    return 0;
}

  当然,这属于老版本的socket程序,最新的socket编程推荐使用getaddrinfo()函数,这个函数是通过一个hints参数来当做地址的返回标准的。具体的getaddrinfo()函数如下:

struct addrinfo {
    int    ai_flags;            //input flags
    int    ai_family;            //address family
    int    ai_socktype;            //SOCK_STREAM, SOCK_DGRAM
    int    ai_protocol;            //socket protocol
    size_t ai_addrlen;            //size of structure pointed to by ai_addr
    char  *ai_cannonname;        //canonical name of host
    struct sockaddr *ai_addr;    //pointer to socket address structure
    struct addrinfo *ai_next;    //next structure in linked list
};

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node,          //host name to connect to or an IP address
                const char *service,    //port number, /etc/services
                const struct addrinfo *hints,    //fill with relevant information, only ai_flags, ai_family, ai_socktype, ai_protocol
                struct addrinfo **res
                );

  上面的服务端程序是通过fork()函数来实现容许多个客户端进行连接的,下面我要用select()函数来实现多个客户端的连接。当然,也同时用了getaddrinfo()这种新特性。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORT "1234"

void *get_in_addr(struct sockaddr *sa) {
    if(sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void) {
    fd_set    master;
    fd_set    read_fds;
    int     fdmax;

    int     listener;
    int     newfd;
    struct sockaddr_storage remoteaddr;
    socklen_t    addrlen;

    char    buf[256];
    int     len;

    char    remoteIP[INET6_ADDRSTRLEN];

    int     yes = 1;
    int     i, j, rv;
//    int     client[100], n;

    struct addrinfo hints, *ai, *p;

    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) {
        perror("getaddrinfo");
        return 1;
    }

    for(p = ai; p != NULL; p = p->ai_next) {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if(listener < 0) {
            continue;
        }

        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

        if(bind(listener, p->ai_addr, p->ai_addrlen) < 0) {
            close(listener);
            continue;
        }

        break;
    }

    if(p == NULL) {
        perror("socket and bind");
        return 2;
    }

    freeaddrinfo(ai);

    if(listen(listener, 10) == -1) {
        perror("listen");
        return 3;
    }

    FD_SET(listener, &master);

    fdmax = listener;

    while(1) {
        printf("HERE!!! BEFORE SELECT\n");
        read_fds = master;
        if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
            perror("select");
            return 4;
        }

        printf("HERE!!! AFTER SELECT\n");

        for(i = 0; i <= fdmax; i++) {
            printf("round i is %d\n", i );
            if(FD_ISSET(i, &read_fds)) {
                printf("isset i is %d\n", i );
                if(i == listener) {
                    addrlen = sizeof(remoteaddr);
                    newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
                    printf("listener is %d, newfd is %d,listener i is %d\n", listener, newfd, i);

                    if(newfd == -1) {
                        perror("accept");
                    }
                    else {
                        FD_SET(newfd, &master);
                        if(newfd > fdmax) {
                            fdmax = newfd;
                        }
                        printf("selectserver: new connection form %s on socket %d\n",
                            inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd);
                    }
                }
                else {
//                    if(i != newfd) {
//                        printf("you are in other‘s zone\n");
//                        goto fail;
//                    }
                    newfd = i;                    //注意这步!!!很重要!!!
                    while(1) {
                        printf("in the newfd round\n");
                        if((len = read(newfd, buf, 256)) <= 0) {
                            perror("read");
                            break;
                        }
                        printf("newfd i is %d\n", i);
//                    while((len = read(newfd, buf, 256)) > 0) {
                        printf("Message form client(%d): %s", len, buf);
                        if(buf[0] == ‘@‘)
                            break;
                        for(j = 0; j < len; j++) {
                                buf[j] = toupper(buf[j]);
                        }
                        write(newfd, buf, len);
                        memset(buf, 0, 256);
//                        printf("remove client on fd %d\n", newfd);
//                        close(newfd);
//                        FD_CLR(newfd, &master);
                    }
//                    FD_CLR(newfd, &read_fds);
                    printf("remove client on fd %d\n", newfd);
                    close(newfd);
                    FD_CLR(newfd, &master);
                }
            }
        }
    }

//    fail: return 8;
}

  上面的代码里有我调试的打印信息,这样就可以更清晰得看到select()函数是如何得阻塞,如何得得知文件描述符的变化。当然,这个函数我也感觉没太弄清楚,等弄清楚之后在准备详细得写一篇关于I/O多路复用的小文章。同时,上面的代码还存在一个问题,那就是服务端只能一个一个得为客户端服务,也就是所谓的同步。

  以上。

参考:《Linux/UNIX系统编程手册》

   《Linux编程技术详解》

   《Beej‘s Guide to Network Programming Using Internet Sockets》

     http://beej.us/guide/bgnet/output/html/multipage/index.html

  

时间: 2024-10-11 07:30:08

最近关于socket编程的理解的相关文章

Socket编程模式理解与对比

本文主要分析了几种Socket编程的模式.主要包括基本的阻塞Socket.非阻塞Socket.I/O多路复用.其中,阻塞和非阻塞是相对于套接字来说的,而其他的模式本质上来说是基于Socket的并发模式.I/O多路复用又主要分析了分析linux和windows下的常用模型.最后,比较这几种Socket编程模式的优缺点,并讨论多线程与Socket的组合使用和服务器开发的常用模式. 阻塞模式 阻塞模式是最基本的Socket编程模式,在各种关于网络编程的书籍中都是入门的例子.就像其名所说,阻塞模式的So

Python Socket 编程——聊天室演示样例程序

上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和client的代码了解主要的 Python Socket 编程模型.本文再通过一个样例来加强一下对 Socket 编程的理解. 聊天室程序需求 我们要实现的是简单的聊天室的样例,就是同意多个人同一时候一起聊天.每一个人发送的消息全部人都能接收到,类似于 QQ 群的功能,而不是点对点的 QQ 好友之间的聊天.例如以下图: 图来自:http://www.ibm.com/developerworks/linux/tu

iOS从零开始学习socket编程——HTTP1.0服务器端

在前一篇文章<iOS从零开始学习socket编程--HTTP1.0客户端>中已经简单的介绍过了Socket编程和一些基本原理.并且实现了简单的iOS客户端(原文地址:http://blog.csdn.net/abc649395594/article/details/45081567) 这里再简单介绍一下如何使用OC搭建socket的服务器端.虽然这并不是一个好的解决方案,通常我们使用Java或者PHP抑或NodeJS来搭建服务器后台.但是还是有必要了解一下OC的做法,顺便加深对Socket编程

Python Socket 编程——聊天室示例程序

原文:Python Socket 编程--聊天室示例程序 上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的理解. 聊天室程序需求 我们要实现的是简单的聊天室的例子,就是允许多个人同时一起聊天,每个人发送的消息所有人都能接收到,类似于 QQ 群的功能,而不是点对点的 QQ 好友之间的聊天.如下图: 图来自:http://www.ibm.com/de

简单理解php的socket编程

php的socket编程算是比较难以理解的东西吧,不过,我们只要理解socket几个函数之间的关系,以及它们所扮演的角色,那么理解起来应该不是很难了,在笔者看来,socket编程,其实就是建立一个网络服务的客户端和服务端,这和mysql的客户端和服务端是一样的,你只要理解mysql的客户端和服务端是怎么一回事,你就应该能够理解下面我要讲的东西吧. 关于socket编程所涉及到的网络协议,什么TCP啊,UDP啊,什么socket三次握手等等,这些网络协议网上有很详细的解释,这里不讲,只截个sock

iOS开发——网络编程OC篇&amp;Socket编程

Socket编程 一.网络各个协议:TCP/IP.SOCKET.HTTP等 网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 其中物理层.数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象: 传输层.会话层.表示层和应用层则被称作主机层,是用户所面向和关心的内容. http协议   对应于应用层 tcp协议    对应于传输层 ip协议     对应于网络层 三者本质上没有可比性.  何况HTTP协议是基于TCP连接的. TCP/IP是传输层协议,主要

Linux Socket编程-(转自吴秦(Tyler))

"一切皆Socket!" 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的.本文的主要内容如下: 1.网络中进程之间如何通信?

[转]Linux的SOCKET编程详解

From : http://blog.csdn.net/hguisu/article/details/7445768 1. 网络中进程之间如何通信 进 程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如 UNIX BSD有:管道(pipe).命名管道(named pipe)软中断信号(signal) UNIX system V有:消息(message).共享存储区(shared mem

Java TCP/UDP socket 编程流程总结

最近正好学习了一点用java socket编程的东西.感觉整体的流程虽然不是很繁琐,但是也值得好好总结一下. Socket Socket可以说是一种针对网络的抽象,应用通过它可以来针对网络读写数据.就像通过一个文件的file handler就可以都写数据到存储设备上一样.根据TCP协议和UDP协议的不同,在网络编程方面就有面向两个协议的不同socket,一个是面向字节流的一个是面向报文的. 对socket的本身组成倒是比较好理解.既然是应用通过socket通信,肯定就有一个服务器端和一个客户端.