Application Programming in Linux Environment (5): Network Programming

 

I. Introduction

       The classic inter-process communication mechanism (IPC) provided by the Linux system: pipes, shared memory, message queues, and semaphores. These mechanisms allow processes running on the same computer to communicate with each other, and for different computers (connected via a network) Inter-process communication introduces a new mechanism: network inter-process communication, processes can communicate with each other through the socket network inter-process communication interface, and many different network protocols can be used to communicate with the socket interface.

1. Five-layer network protocol stack:

Application layer protocols: FTP, HTTP, SMTP

Transport layer protocol: TCP protocol, UDP protocol

Network layer protocol: IP protocol

The TCP protocol and UDP protocol described in this chapter belong to the transport layer. For the specific functions of the five-layer structure, please refer to: https://blog.csdn.net/weixin_37719279/article/details/82846226

2. The difference between TCP and UDP protocols

TCP provides connection-oriented reliable services to the upper layer, and UDP provides connectionless unreliable services to the upper layer. Although UDP is not as accurate as TCP transmission, it is often used in scenarios with high real-time requirements.

3. TCP's three-way handshake and four waved hands

(1) Three handshake

The first handshake: When establishing a connection, the client sends a syn packet (syn=j) to the server, and enters the SYN_SENT state, waiting for the server to confirm; SYN: Synchronize Sequence Numbers.

The second handshake: the server receives the syn packet, must confirm the client's SYN (ack=j+1), and at the same time send a SYN packet (syn=k), that is, the SYN+ACK packet, and the server enters the SYN_RECV state;

The third handshake: the client receives the SYN+ACK packet from the server, and sends an acknowledgment packet ACK (ack=k+1) to the server. After the packet is sent, the client and server enter the ESTABLISHED (TCP connection successful) state, which is completed three times shake hands.

(2) Wave four times

a. The client process sends a connection release message and stops sending data. Release the header of the data message, FIN=1, its sequence number is seq=u (equal to the sequence number of the last byte of the data that has been transmitted before plus 1), at this time, the client enters FIN-WAIT-1 (terminate waiting 1) Status. TCP stipulates that even if the FIN segment does not carry data, it will consume a sequence number.
b. The server receives the connection release message and sends an acknowledgment message, ACK=1, ack=u+1, and brings its own serial number seq=v. At this time, the server enters CLOSE-WAIT (close waiting )status. The TCP server notifies the high-level application process that the client is released in the direction of the server. At this time, it is in a half-closed state, that is, the client has no data to send, but if the server sends data, the client still has to accept it. This state will continue for a while, that is, the duration of the entire CLOSE-WAIT state.
c. After the client receives the confirmation request from the server, at this time, the client enters the FIN-WAIT-2 (termination waiting 2) state, waiting for the server to send a connection release message (before that, it needs to accept the last data).
d. After the server sends the final data, it sends a connection release message to the client, FIN=1, ack=u+1, because it is in the semi-closed state, the server is likely to send some more data, assuming this time The sequence number is seq=w. At this time, the server enters the LAST-ACK (last confirmation) state, waiting for the client's confirmation.
e. After the client receives the connection release message from the server, it must send an acknowledgment, ACK=1, ack=w+1, and its own serial number is seq=u+1, at this time, the client enters TIME- WAIT (time waiting) status. Note that the TCP connection has not been released at this time. It must pass 2∗∗MSL (the longest message segment life) time, when the client cancels the corresponding TCB before it enters the CLOSED state.
f. As long as the server receives the confirmation from the client, it immediately enters the CLOSED state. Similarly, after the TCB is withdrawn, the TCP connection is ended. As you can see, the server ends the TCP connection earlier than the client.

Detailed reference: https://www.cnblogs.com/bj-mr-li/p/11106390.html

Two: network programming

1. Addressing

(1) Byte order

Applications sometimes need to switch between processor byte order and network byte order. For applications, there are 4 functions used to convert between processor byte order and network byte order.

"H" means "host" byte order, "n" means "network" byte order, "l" means "long" (ie 4 bytes) integer, and "s" means "short" (ie 2 bytes) Integer

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32); 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32);  返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16);  返回值:以主机字节序表示的16位整数

(2) Address format

        An address identifies the socket endpoint of a specific communication domain. The address format is related to this specific communication domain. In order to enable addresses in different formats to be passed into the socket function, the address will be forced into a general address structure:

struct sockaddr{
    unisgned short  sa_family; /*地址族*/
    char sa_data[14];          /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};

Due to system compatibility issues, the new address structure is used to indicate:

struct sockaddr_in{
    unsigned short          sin_family;     /*地址族*/
    unsigned short int      sin_port;       /*端口号*/
    struct in_addr          sin_addr;       /*IP 地址*/
    unsigned char           sin_zero[8];    /*填充0 以保持与struct sockaddr 同样大小*/
}

(3) Address format conversion

Generally, users use dotted decimal notation (or a colon-separated IPv6 address) when expressing addresses, but binary values ​​are used in commonly used socket programming, which requires combining these two The value is converted. The functions used in IPv4 here are inet_aton(), inet_addr() and inet_ntoa(), and IPv4 and IPv6 compatible functions are inet_pton() and inet_ntop().

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr)
int inet_ntop(int family, void *addrptr, char *strptr, size_t len)

family:{
    AF_INET:IPv4 协议
    AF_INET6:IPv6 协议
}
strptr:要转化的值
addrptr:转化后的地址
len:转化后值的大小
返回值:成功0、出错-1

inet_aton is an improved method to convert a string IP address into a 32-bit network serial IP address.

1 The input parameter string contains the ASCII IP address.
2 The output parameter addr is the structure to be updated with the new IP address.

If this function succeeds, the return value of the function is non-zero. If the input address is incorrect, zero will be returned. There is no error code stored in errno when using this function, so its value will be ignored.

Example:

struct sockaddr_in socket_server_addr;

socket_server_addr.sin_family   = AF_INET;
/*主机字节序转换为网络字节序*/
socket_server_addr.sin_port     = htons(SERVER_PORT);
if (inet_aton(“192.168.1.100”, &socket_server_addr.sin_addr) == 0)
{
    printf("invalid server ip\n");
    return -1;
}

(4) Name address conversion

Generally, people are reluctant to memorize long IP addresses during use. Especially when IPv6 is used, the address length is up to 128 bits, and then it is even more impossible to memorize such long IP addresses again and again. Therefore, using the host name would be a good choice. In Linux, there are also some functions that can realize the conversion of host name and address, the most common ones are gethostbyname(), gethostbyaddr() and getaddrinfo(), etc., all of which can realize the conversion between IPv4 and IPv6 addresses and host names. Among them, gethostbyname() converts a host name into an IP address, and gethostbyaddr() is an inverse operation, which converts an IP address into a host name. In addition, getaddrinfo() can automatically identify IPv4 addresses and IPv6 addresses.

struct hostent
{
    char *h_name;/*正式主机名*/
    char **h_aliases;/*主机别名*/
    int h_addrtype;/*地址类型*/
    int h_length;/*地址字节长度*/
    char **h_addr_list;/*指向 IPv4 或 IPv6 的地址指针数组*/
}

struct addrinfo
{
    int ai_flags;/*AI_PASSIVE, AI_CANONNAME;*/
    int ai_family;/*地址族*/
    int ai_socktype;/*socket 类型*/
    int ai_protocol;/*协议类型*/
    size_t ai_addrlen;/*地址字节长度*/
    char *ai_canonname;/*主机名*/
    struct sockaddr *ai_addr;/*socket 结构体*/
    struct addrinfo *ai_next;/*下一个指针链表*/
}

ai_flags{
    AI_PASSIVE:该套接口是用作被动地打开
    AI_CANONNAME:通知 getaddrinfo 函数返回主机的名字
}
ai_family{
    AF_INET:IPv4 协议
    AF_INET6:IPv6 协议
    AF_UNSPEC:IPv4 或 IPv6 均可
}
ai_socktype {
    SOCK_STREAM:字节流套接字 socket(TCP)
    SOCK_DGRAM:数据报套接字 socket(UDP)
}
ai_protocol{
    IPPROTO_IP:IP 协议
    IPPROTO_IPV4:IPv4 协议 4 IPv4
    IPPROTO_IPV6:IPv6 协议
    IPPROTO_UDP:UDP
    IPPROTO_TCP:TCP
}
#include <netdb.h>

struct hostent *gethostbyname(const char *hostname)
hostname:主机名
返回值:成功返回hostent类型指针、错误-1

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints,struct addrinfo **result)
node:网络地址或者网络主机名
service:服务名或十进制的端口号字符串
hints:服务线索
result:返回结果
返回值:成功0、错误-1

(1) Usually, before calling getaddrinfo() on the server side, ai_flags sets AI_PASSIVE, which is used in the bind() function (used to bind the port and address, which will be discussed later), and the host name nodename is usually set to NULL.
(2) When the client calls getaddrinfo(), ai_flags generally does not set AI_PASSIVE, but the host name nodename and service name servname (port) should not be empty.
(3) Even if ai_flags is not set to AI_PASSIVE, the retrieved address can be bound. In many programs, ai_flags is directly set to 0, that is, the three flag bits are not set. In this case, as long as the hostname and servname are set without problems, Can be bound correctly.

Example:

/* getaddrinfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    struct addrinfo hints, *res = NULL;
    int rc;
    memset(&hints, 0, sizeof(hints));

    /*设置 addrinfo 结构体中各参数 */
    hints.ai_flags = AI_CANONNAME;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;
    /*调用 getaddinfo 函数*/
    rc = getaddrinfo("localhost", NULL, &hints, &res);
    if (rc != 0)
    {
        perror("getaddrinfo");
        exit(1);
    }
    else
    {
        printf("Host name is %s\n", res->ai_canonname);
    }
    exit(0);
}

2. Socket basic programming

(1) Socket descriptor

Create a socket:

#include <sys/socket.h>
int socket(int domain, int type,int protocol);

domain:确定通信的特性,包括地址格式
        AF_INET:IPv4因特网域
        AF_INET6:IPv6因特网域
        AF_UNIX:UNIX域
        AF_UPSPEC:未指定
type:确定套接字类型
        SOCK_DGRAM:固定长度的,无连接的,不可靠的报文传递(UDP)
        SOCK_RAW:IP协议的数据报接口
        SOCK_SEQPACKET:固定长度的,有序的,可靠的,面向连接的报文传递
        SOCK_STREAM:有序的,可靠的,双向的,面向连接的字节流(TCP)
protocol:通常为0,表示为给定的域和套接字类型选择默认协议

Close the socket:

#include <sys/socket.h>
int shutdown(int sockfd, int how);

how:SHUT_RD(关闭读端),无法从套接字读取数据
     SHUT_WR(关闭写端),无法从套接字发送数据
     SHUT_RDWR,无法读取和发送数据

(2) Server associate socket and address

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

示例:
struct sockaddr_in socket_server_addr;
duty_socket = socket(AF_INET, SOCK_STREAM, 0);
/* 服务器端填充 sockaddr_in结构 */
socket_server_addr.sin_family   = AF_INET;
/*端口号转换为网络字节序*/
socket_server_addr.sin_port     = htons(SERVER_PORT);
/*接收本机所有网口的数据*/
socket_server_addr.sin_addr.s_addr  = INADDR_ANY;
memset(socket_server_addr.sin_zero, 0, 8);

/* 捆绑sockfd描述符 */
ret = bind(duty_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));

(3) The server calls the listen function to accept the connection request

int listen(int sockfd,int backlog);

ret = listen(duty_socket, C_QUEUE);

sockfd是bind后的文件描述符。
backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。

(4) The server obtains the connection request and establishes the connection

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


int customer_socket;
customer_socket = accept(duty_socket, (struct sockaddr *)&socket_client_addr, &addr_len);

sockfd是listen后的文件描述符。
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。

(5) The client establishes a connection

int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);

ret = connect(client_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));


可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd是socket函数返回的文件描述符。
serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen是serv_addr的长度 
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。

(6) Send data

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

sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据


ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr 表示目地机的IP地址和端口号信息,
addrlen 常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

(7) Accept data

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len 指明buf的长度;
flags 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。


ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);


recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen 常置为sizeof (struct sockaddr)。

TCP example:

The instance is divided into two parts: the client and the server. The server first establishes a socket, then binds to the local port, and then begins to receive connection requests from the client and establish a connection with it, and then to receive the client The message sent. The client calls the connect() function to establish a connection after the socket is established.

Server:

/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main()
{
    struct sockaddr_in server_sockaddr,client_sockaddr;
    int sin_size,recvbytes;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    /*建立 socket 连接*/
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        perror("socket");
        exit(1);
    }
    printf("Socket id = %d\n",sockfd);
    /*设置 sockaddr_in 结构体中相关参数*/
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    /*绑定函数 bind()*/
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,
    sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    printf("Bind success!\n");
    /*调用 listen()函数,创建未处理请求的队列*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    /*调用 accept()函数,等待客户端的连接*/
    if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size)) == -1)
    {
        perror("accept");
        exit(1);
    }
    /*调用 recv()函数接收客户端的请求*/
    memset(buf , 0, sizeof(buf));
    if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1)
    {
        perror("recv");
        exit(1);
    }
    printf("Received a message: %s\n", buf);
    close(sockfd);
    exit(0);
}

Client:

/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 4321
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
    int sockfd,sendbytes;
    char buf[BUFFER_SIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;
    if(argc < 3)
    {
        fprintf(stderr,"USAGE: ./client Hostname(or ip address) Text\n");
        exit(1);
    }
    /*地址解析函数*/
    if ((host = gethostbyname(argv[1])) == NULL)
    {
        perror("gethostbyname");
        exit(1);
    }
    memset(buf, 0, sizeof(buf));
    sprintf(buf, "%s", argv[2]);
    /*创建 socket*/
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    /*设置 sockaddr_in 结构体中相关参数*/
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(serv_addr.sin_zero), 8);
    /*调用 connect 函数主动发起对服务器端的连接*/
    if(connect(sockfd,(struct sockaddr *)&serv_addr,
    sizeof(struct sockaddr))== -1)
    {
        perror("connect");
        exit(1);
    }
    /*发送消息给服务器端*/
    if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1)
    {
        perror("send");
        exit(1);
    }
    close(sockfd);
    exit(0);
}

result:

$ ./server
Socket id = 3
Bind success!
Listening....
Received a message: Hello,Server!
$ ./client localhost(或者输入 IP 地址) Hello,Server!

3. Advanced network programming

In actual situations, people often encounter situations where multiple clients connect to the server. Since the previously introduced connet(), recv() and send() are all blocking functions, if the resource is not ready, the process that calls this function will enter a sleep state, so that it cannot handle I/O multiplexing The situation is used. This section gives two solutions to solve I/O multiplexing, these two functions are fcntl() and select() learned before.

(1)fcntl()

The function fcntl() provides the following programming features for socket programming.

  • Non-blocking I/O: You can set cmd to F_SETFL and lock to O_NONBLOCK.
  • Asynchronous I/O: You can set cmd to F_SETFL and lock to O_ASYNC.
/* net_fcntl.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#define PORT 1234
#define MAX_QUE_CONN_NM 5
#define BUFFER_SIZE 1024
int main()
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, recvbytes, flags;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sockfd,MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    /* 调用 fcntl()函数给套接字设置非阻塞属性 */
    flags = fcntl(sockfd, F_GETFL);
    if (flags < 0 || fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) < 0)
    {
        perror("fcntl");
        exit(1);
    }
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);
        if ((client_fd = accept(sockfd,(struct sockaddr*)&client_sockaddr, &sin_size)) < 0)
        {
            perror("accept");
            exit(1);
        }
        if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) < 0)
        {
            perror("recv");
            exit(1);
        }
        printf("Received a message: %s\n", buf);
    } /*while*/
    close(client_fd);
    exit(1);
}



$ ./net_fcntl
Listening....
accept: Resource temporarily unavailable
可以看到,当 accept()的资源不可用(没有任何未处理的等待连接的请求)时,程序就会自动返回。

(2)select()

Although the fcntl() function can be used to realize non-blocking I/O or signal-driven I/O, in actual use, it often tests whether the resource is ready for circulation, which greatly increases the consumption of unnecessary CPU resources. Here you can use the select() function to solve this problem. At the same time, use the select() function to set the waiting time, which can be said to be more powerful.

/* net_select.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define MAX_QUE_CONN_NM 5
#define MAX_SOCK_FD FD_SETSIZE
#define BUFFER_SIZE 1024
int main()
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, count;
    fd_set inset, tmp_inset;
    int sockfd, client_fd, fd;
    char buf[BUFFER_SIZE];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("listening....\n");
    /*将调用 socket()函数的描述符作为文件描述符*/
    FD_ZERO(&inset);
    FD_SET(sockfd, &inset);
    while(1)
    {
        tmp_inset = inset;
        sin_size=sizeof(struct sockaddr_in);
        memset(buf, 0, sizeof(buf));
        /*调用 select()函数*/
        if (!(select(MAX_SOCK_FD, &tmp_inset, NULL, NULL, NULL) > 0))
        {
            perror("select");
        }
        for (fd = 0; fd < MAX_SOCK_FD; fd++)
        {
            if (FD_ISSET(fd, &tmp_inset) > 0)
            {
                if (fd == sockfd)
                { /* 服务端接收客户端的连接请求 */
                    if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size))== -1)
                    {
                        perror("accept");
                        exit(1);
                    }
                    FD_SET(client_fd, &inset);
                    printf("New connection from %d(socket)\n", client_fd);
                }
                else /* 处理从客户端发来的消息 */
                {
                    if ((count = recv(client_fd, buf, BUFFER_SIZE, 0)) > 0)
                    {
                        printf("Received a message from %d: %s\n",
                        client_fd, buf);
                    }
                    else
                    {
                        close(fd);
                        FD_CLR(fd, &inset);
                        printf("Client %d(socket) has left\n", fd);
                    }
                }
            } /* end of if FD_ISSET*/
        } /* end of for fd*/
    } /* end if while while*/
    close(sockfd);
    exit(0);
}


$ ./server
listening....
New connection from 4(socket) /* 接受第一个客户端的连接请求*/
Received a message from 4: Hello,First! /* 接收第一个客户端发送的数据*/
New connection from 5(socket) /* 接受第二个客户端的连接请求*/
Received a message from 5: Hello,Second! /* 接收第二个客户端发送的数据*/
Client 4(socket) has left /* 检测到第一个客户端离线了*/
Client 5(socket) has left /* 检测到第二个客户端离线了*/
$ ./client localhost Hello,First! & ./client localhost Hello,Second

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/107513609