alin的学习之路(Linux网络编程:三)(高并发服务器-多线程、TCP通信时序状态、多路IO转接select概述)

alin的学习之路(Linux网络编程:三)(高并发服务器-多线程、TCP通信时序状态、多路IO转接select概述)

0. 一些小的注意事项

1. SIGCHLD

  1. SIGCHLD 只要子进程状态发生变化就会产生,不仅仅是子进程死亡,还有子进程暂停和继续

  2. SIGCHLD 默认处理动作是忽略

  3. 用信号回收子进程时 要使用while循环回收多次 。原因是信号不支持排队,多个子进程结束只有一个信号。

2. 线程和进程注意

  1. 兄弟线程之间可以使用 pthread_join 回收
  2. 进程仅父进程可以回收子进程,其他都不可以
  3. 主子线程之间共享0-4G进程地址空间,父子进程之间独占0-4G进程地址空间

1. 高并发服务器-多线程

程序流程

  1. Socket()创建套接字

  2. Bind()绑定IP和端口号

  3. Listen()设置最大监听数

  4. while(1){

  5. Accept()阻塞等待客户端接入

  6. 创建子线程,给子线程传入 包含cfd和clt_addr的结构体

  7. 子线程分离

    子线程:

    1. Read()接收客户端发送的数据,如果Read()的返回值为0,Close(cfd);
    2. 小写转大写 toupper()
    3. Write()将处理完成的数据发送给客户端
  8. }

  9. Close(lfd);

在主进程中定义一个结构体数组,每创建一个子线程存放一个结构体到数组中
struct s_info{
    struct sockaddr_in clt_addr;
    int cfd;
};

2. TCP通信时序状态转换图

alin的学习之路(Linux网络编程:三)(高并发服务器-多线程、TCP通信时序状态、多路IO转接select概述)_第1张图片

alin的学习之路(Linux网络编程:三)(高并发服务器-多线程、TCP通信时序状态、多路IO转接select概述)_第2张图片

  • 主动发起连接请求端
    • CLOSE — 发送SYN — SYN_SENT — 接收SYN和ACK,发送ACK — ESTABLISHED(数据通信状态)
  • 主动断开连接请求端
    • ESTABLISHED(数据通信状态)— 发送FIN — FIN_WAIT_1 — 接收ACK — FIN_WAIT_2(半关闭)— 接收FIN — TIME_WAIT(只出现在主动关闭一方)— 等待2MSL时长 — CLOSE
  • 被动接受连接请求端
    • CLOSE — LISTEN — 接收SYN 发送 ACK 和 SYN — SYN_RCVD — 接收ACK — ESTABLISHED(数据通信状态)
  • 被动关闭连接请求端
    • ESTABLISHED(数据通信状态)— 接收FIN — ESTABLISHED(数据通信状态)— 发送ACK — CLOSE_WAIT(对应半关闭)— 发送FIN — LAST_ACK — 接收ACK — CLOSE

3. 2MSL

  • 一定出现在主动断开连接请求端,对应TIME_WAIT状态
  • 目的:保证最后一个ACK能够被成功接收。(等待期间,若对端没有接收到ACK,对端会重新发送FIN,本端再重发ACK)

端口复用

因为以上2MSL的存在,如果服务器处于TIME_WAIT状态,要等待2MSL时间才能重启。

不等待2MSL可以使用端口复用。
端口复用在客户端和服务器端都可以使用

int opt = 1;   //设置端口可以复用
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));

4. 多路IO转接并发服务器

使用select帮助监听文件描述符,不需要再创建进程或线程来完成操作。

select相关函数

#include 
int select(int nfds, fd_set *readfds, fd_set *writefds,
         fd_set *exceptfds, struct timeval *timeout);
	nfds: 监听的所有的文件描述符中,最大的文件描述+1
  	readfds: 读 文件描述符监听集合 传入、传出参数
  	writefds  写 文件描述符监听集合 传入、传出参数 NULL
  	exceptfds 异常 文件描述符监听集合 传入、传出参数 NULL
  	timeout: 设置超时时长。
 		> 0: 设置监听时长
 		NULL:阻塞监听
 		0: 非阻塞监听, 需要轮询。
  	返回值:
 		> 0: 所有监听集合(3个)中, 满足对应事件的文件描述符的【总】个数。
		0 : 没有满足监听条件的文件描述符。
		-1: errno
    
// 清空一个文件描述符集合
void FD_ZERO(fd_set *set);
// 例:
fd_set read_set;
FD_ZERO(&read_set);

// 将待监听的文件描述符, 添加到监听集合中。
void FD_SET(int fd, fd_set *set);
// 例:
FD_SET(3, &read_set);
FD_SET(7, &read_set);
FD_SET(5, &read_set);

// 将一个文件描述符从监听集合中,移除
void FD_CLR(int fd, fd_set *set);
// 例:
FD_CLR(7, &read_set);

// 判断一个文件描述符,是否在 监听集合中
int FD_ISSET(int fd, fd_set *set);
返回值:在:1;  不在:0
// 例:
FD_ISSET(4, &read_set);  ----> 0

代码实现:

#include 
#include 
#include 
#include 
#include 
#include 

#include "wrap.h"

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int i, j, n, nready;

    int maxfd = 0;
    int listenfd, connfd;
    char buf[BUFSIZ];         /* #define INET_ADDRSTRLEN 16 */

    struct sockaddr_in clie_addr, serv_addr;
    socklen_t clie_addr_len;

    fd_set rset, allset;                            /* rset 读事件文件描述符集合 allset用来暂存 */

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);  
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family= AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port= htons(SERV_PORT);
    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    Listen(listenfd, 128);

    maxfd = listenfd;

    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);                                  /* 构造select监控文件描述符集 */

    while (1) {   
        rset = allset;                                          /* 每次循环时都从新设置select监控信号集 */
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (nready < 0)
            perr_exit("select error");

        if (FD_ISSET(listenfd, &rset)) {                        /* 说明有新的客户端链接请求 */

            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);       /* Accept 不会阻塞 */

            FD_SET(connfd, &allset);                            /* 向监控文件描述符集合allset添加新的文件描述符connfd */

            if (maxfd < connfd)
                maxfd = connfd;

            if (0 == --nready) 
                continue;
        } 

        for (i = listenfd+1; i <= maxfd; i++) {                 /* 检测哪个clients 有数据就绪 */

            if (FD_ISSET(i, &rset)) {
                if ((n = Read(i, buf, sizeof(buf))) == 0) {    /* 当client关闭链接时,服务器端也关闭对应链接 */
                    Close(i);
                    FD_CLR(i, &allset);                        /* 解除select对此文件描述符的监控 */

                } else if (n > 0) {

                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Write(i, buf, n);
                }
            }
        }
    }

    Close(listenfd);

    return 0;
}

你可能感兴趣的:(Linux,socket,多路IO转接select)