• OS | Socket


    TCP

    创建socket:

    1 int socket(int domain, int type, int protocol);

    AF = Address Family
    PF = Protocol Family

    AF_INET IPv4 Internet protocols ip(7)
    AF_INET6 IPv6 Internet protocols ipv6(7)

    TCP: SOCK_STREAM

    UDP: SOCK_DGRAM

    RAW: SOCK_RAW (Provides raw network protocol access)

    绑定:

    1 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
    程序员不应操作sockaddr,sockaddr是给操作系统用的
    程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。

    一般的用法为:
    程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数

     1 include <netinet/in.h>
     2 
     3 struct sockaddr {
     4     unsigned short    sa_family;    // 2 bytes address family, AF_xxx
     5     char              sa_data[14];     // 14 bytes of protocol address
     6 };
     7 
     8 // IPv4 AF_INET sockets:
     9 
    10 struct sockaddr_in {
    11     short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
    12     unsigned short   sin_port;    // 2 bytes e.g. htons(3490)
    13     struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below
    14     char             sin_zero[8];     // 8 bytes zero this if you want to
    15 };
    16 
    17 struct in_addr {
    18     unsigned long s_addr;          // 4 bytes load with inet_pton()
    19 };

    htons 和htonl用来将主机字节顺序转换为网络字节顺序。(小端和大端)

    监听:

    1 int listen(int sockfd, int backlog);

    backlog这个参数涉及到一些网络的细节。在进程正处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
    毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。

    接收:

    1 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    返回的是客户端的socket和地址;

    发送数据:

    1 ssize_t send(int sockfd, const void *buf, size_t len, int flags);

    测试1

    当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。
    根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。若不想客户端退出可以把SIGPIPE设为SIG_IGN

    如: signal(SIGPIPE,SIG_IGN);
    这时SIGPIPE交给了系统处理。

    设置之后可以看到错误是 error: Broken pipe。

    当服务器进程终止时,无论应用程序是否显式关闭了socket, OS会负责在进程结束时关闭所有的文件描述符,对于socket,则会发送一个FIN包到对面。假如服务器进程是异常终止的,发送FIN包是OS代劳的,服务器进程已经不复存在,当服务器再次收到该socket的消息时,会回应RST(因为拥有该socket的进程已经终止)。客户端进程对收到RST的socket调用write时,操作系统会给客户端进程发送SIGPIPE,默认处理动作是终止进程。

    测试2

     每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态。接收缓冲区把数据缓存入内核,应用进程一直没有调用read进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内。再啰嗦一点,不管进程是否读取socket,对端发来的数据都会经由内核接收并且缓存到socket的内核接收缓冲区之中。read所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,仅此而已。进程调用send发送的数据的时候,最简单情况(也是一般情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。换句话说,send返回之时,数据不一定会发送到对端去(和write写文件有点类似),send仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中。

    我用服务器每隔10秒接收一次数据,客户端每隔1秒发送一次数据,服务器读出的就是客户端10次发送的总数据。

    测试3

    对于blocking的write有个特例:当write正阻塞等待时对面关闭了socket,则write则会立即将剩余缓冲区填满并返回所写的字节数,再次调用则write失败(connection reset by peer)。

    我直接杀掉服务器进程,客户端再调用send时,出现error: Connection reset by peer。

    测试4

    客户端持续发送,服务器端能够一直确保有序和不丢包。

    建立一个socket,通过getsockopt获取缓冲区的值如下:
    发送缓冲区大小:SNDBufSize = 16384
    接收缓冲区大小:RCVBufSize = 87380

    设置小于2240的值,缓冲区大小为2240.大于2240的值,缓冲区大小为设置值的2倍。

    不知道为什么,发送一定数据量之后, 客户端陷入等待,但是此时发送量并不等于缓冲区大小。 

    测试5

    设置了listen的backlog参数为2,同时开启了5个client进程,都能够连接成功,和理解的不同,不知道为什么?

    异步socket

     linux下有五种I/O模型:

    1. 阻塞I/O(blocking I/O)
    2. 非阻塞I/O (nonblocking I/O)
    3. I/O复用(select 和poll) (I/O multiplexing)
    4. 信号驱动I/O (signal driven I/O (SIGIO))
    5. 异步I/O (asynchronous I/O (the POSIX aio_functions))

    每次都要把所有的socket加到read flags。 不设置的话,当没有读事件时,select会把read flags该位清空,之后没有socket可检查,所以会一直超时。

    用FD_ISSET判断可以判断server socket或者client socket是否可读。server socket可读,证明有新连接。client socket可读证明有新数据。

    select也会改变等待时间,所以调用select之前都要重新初始化timeval。

    1 int select(int nfds, fd_set *readfds, fd_set *writefds,
    2                   fd_set *exceptfds, struct timeval *timeout);
    3 
    4 void FD_CLR(int fd, fd_set *set);
    5 int  FD_ISSET(int fd, fd_set *set);
    6 void FD_SET(int fd, fd_set *set);
    7 void FD_ZERO(fd_set *set);

     例子

    阻塞的比较简单,udp也比较简单。所以这里展示的是非阻塞的。

    client

     1 #include <stdio.h>
     2 #include <sys/types.h> //socket
     3 #include <sys/socket.h> //socket
     4 #include <string.h> //strerror
     5 #include <errno.h> //errno
     6 #include <netinet/in.h> // sockaddr_in
     7 #include <arpa/inet.h> //inet_ntoa
     8 #include <unistd.h>
     9 #include <signal.h>
    10 
    11 enum {
    12     SERVER_PORT = 8888,
    13     BUFFER_SIZE = 1024,
    14 };
    15 
    16 int main() {
    17     int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    18     if (clientSocket < 0) {
    19         printf("error: %s
    ", strerror(errno)); 
    20         return -1;
    21     }
    22     sockaddr_in serverAddr;
    23     bzero(&serverAddr, sizeof(serverAddr));
    24     serverAddr.sin_family = AF_INET; //ipv4
    25     serverAddr.sin_port = htons(SERVER_PORT);
    26     serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // server ip, any ips
    27 
    28     if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
    29         printf("error: %s
    ", strerror(errno)); 
    30         close(clientSocket);
    31         return -1;
    32     }
    33 
    34     char buffer[BUFFER_SIZE + 1];
    35 
    36     sprintf(buffer, "hello0");
    37     signal(SIGPIPE,SIG_IGN);
    38     int osBufferSize;
    39     int osBufferSizeBytes = sizeof(osBufferSize);
    40     if (getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, 
    41                 (void *)&osBufferSize,(socklen_t*)&osBufferSizeBytes) < 0){
    42         printf("error: %s
    ", strerror(errno)); 
    43         return -1;
    44     }
    45     printf("the sender buffer len: %d
    ", osBufferSize);
    46 
    47     int i = 1;
    48     int sendBytes = 0;
    49     while (send(clientSocket, buffer, strlen(buffer), 0) >= 0) {
    50         sendBytes += strlen(buffer);
    51         sprintf(buffer, "h%0d", i++);
    52         printf("send: %d
    ", sendBytes);
    53         //sleep(1);
    54     }
    55     printf("error: %s
    ", strerror(errno));
    56     close(clientSocket);
    57     return 0;
    58 }

    server

      1 #include <stdio.h>
      2 #include <sys/types.h> //socket
      3 #include <sys/socket.h> //socket
      4 #include <string.h> //strerror
      5 #include <errno.h> //errno
      6 #include <netinet/in.h> // sockaddr_in
      7 #include <arpa/inet.h> //inet_ntoa
      8 #include <unistd.h>
      9 #include <fcntl.h>
     10 
     11 enum {
     12     SERVER_PORT = 8888,
     13     BUFFER_SIZE = 32,
     14 };
     15 
     16 bool setSocketRcvBufferSize(int sock, int osBufferSize) {
     17     int osBufferSizeBytes = sizeof(osBufferSize);
     18     if (setsockopt(sock, SOL_SOCKET, /*SO_SNDBUF*/SO_RCVBUF, 
     19                 (void *)&osBufferSize, osBufferSizeBytes) < 0){
     20         printf("error: %s
    ", strerror(errno)); 
     21         return false;
     22     }
     23     if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, 
     24                 (void *)&osBufferSize,(socklen_t*)&osBufferSizeBytes) < 0){
     25         printf("error: %s
    ", strerror(errno)); 
     26         return false;
     27     }
     28     printf("the recevice buffer len: %d
    ", osBufferSize);
     29     return true;
     30 }
     31 
     32 void setNonblocking(int sock) {
     33     int ret = fcntl(sock,F_GETFL, 0);              // Get socket flags  
     34     fcntl(sock,F_SETFL, ret | O_NONBLOCK);   // Add non-blocking flag  
     35 }
     36 
     37 int main() {
     38     int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
     39     if (serverSocket < 0) {
     40         printf("error: %s
    ", strerror(errno)); 
     41         return -1;
     42     }
     43 
     44     setNonblocking(serverSocket);
     45 
     46     sockaddr_in serverAddr;
     47     bzero(&serverAddr, sizeof(serverAddr));
     48     serverAddr.sin_family = AF_INET; //ipv4
     49     serverAddr.sin_port = htons(SERVER_PORT);
     50     serverAddr.sin_addr.s_addr = INADDR_ANY; // server ip, any ips
     51 
     52     if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
     53         printf("error: %s
    ", strerror(errno)); 
     54         close(serverSocket);
     55         return -1;
     56     }
     57 
     58     if (listen(serverSocket, 1) < 0) { // almost two connections at the same time
     59         printf("error: %s
    ", strerror(errno)); 
     60         close(serverSocket);
     61         return -1;
     62     }
     63 
     64     setSocketRcvBufferSize(serverSocket, 4096);    
     65 
     66     char buffer[BUFFER_SIZE + 1];
     67     int recvLen = 0;
     68     struct timeval waitt;
     69     fd_set readFlags;
     70     int clientSocket;
     71     int maxSocket = serverSocket;
     72     while (true) {
     73         FD_ZERO(&readFlags);
     74         FD_SET(serverSocket, &readFlags);
     75         if (clientSocket > 0) FD_SET(clientSocket, &readFlags);
     76         waitt.tv_sec = 2;
     77         waitt.tv_usec = 0;
     78         int stat = select(maxSocket + 1, &readFlags, NULL, NULL, &waitt); // waitt will be change to zero
     79         if (stat < 0) {
     80             printf("error: %s
    ", strerror(errno));
     81             continue;
     82         } else if (stat == 0) {
     83             printf("select timeout
    ");
     84             continue;
     85         }
     86 
     87         if (FD_ISSET(serverSocket, &readFlags)) { 
     88             sockaddr_in clientAddr;
     89             int clientAddrSize = sizeof(clientAddr);
     90             clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrSize);
     91             if (clientSocket > maxSocket) maxSocket = clientSocket;
     92             printf("client: %s:%d
    ", inet_ntoa(clientAddr.sin_addr), 
     93                     ntohs(clientAddr.sin_port));
     94         } 
     95 
     96         if (clientSocket < 0) continue;
     97 
     98         if (FD_ISSET(clientSocket, &readFlags)) {
     99             FD_CLR(clientSocket, &readFlags);
    100             bzero(buffer, BUFFER_SIZE);
    101             recvLen = recv(clientSocket, buffer, BUFFER_SIZE, 0);
    102             buffer[recvLen] = '';
    103             printf("recv: %s
    ", buffer);
    104         }
    105     }
    106     close(clientSocket);
    107     close(serverSocket);
    108     return 0;
    109 }

     

  • 相关阅读:
    面对祖传屎山代码应该采用的5个正确姿势
    一行代码卖出570美元, 天价代码的内幕
    漫画 | 悲催的中国式软件开发
    看看我每天的工作,你们这些程序员都是“辣鸡”!
    漫画 | 浏览器一个比一个“无耻”
    程序员应该造的五大轮子
    我所尊敬的三位女程序员
    重磅!七国首脑会议决定制裁Go语言!
    漫画 | C语言哭了,过年回家,只有我还没对象
    漫画 | CPU战争40年,真正的王者终于现身!
  • 原文地址:https://www.cnblogs.com/linyx/p/3821355.html
Copyright © 2020-2023  润新知