Linux网络编程

Posted by DEVIN on Sun, Jun 18, 2023

查看端口占用情况

netstat -tunlp

-t (tcp) 仅显示tcp相关选项 -u (udp) 仅显示udp相关选项

-n 拒绝显示列名,能显示数字的全部转化为数字

-l 仅显示出在listen(监听)的服务状态

-p 显示潜力相关链接的程序名

linux查看端口被哪个进程占用的方法

本机地址

127.0.0.1: 这个地址通常分配给loopback接口,loopback是一个特殊的网络IP,可以理解为虚拟网卡,用于本机中各个应用之间的网络交互,只要操作系统网络组建正常,loopback就能工作。所以这里需要明白使用127.0.0.1进行通信时,必须要保证client和server在同一台机器上

INADDR_ANY: 从字面上的any可以看出,转换过来其实是0.0.0.0,泛指本机的意思,也就是标识本机的所有IP,因为有些机器是不止一块网卡的,在多网卡的情况下,这个就表示所有的网卡IP地址的意思,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。

如果现在有两台PC在同一个局域网内,分别为PC1与PC2,PC1上有一个网卡,IP地址为192.168.10.128

  • PC1中sever监听127.0.0.1,则PC1中的client可以连上127.0.0.1192.168.10.128连不上;而PC2中client都连不上。
  • PC1中sever监听192.168.10.128,则PC1中的client可以连上192.168.10.128127.0.0.1连不上;而PC2中client能连上192.168.10.128
  • PC1中sever监听0.0.0.0,则PC1中的client可以连上127.0.0.1192.168.10.128,PC2中的client能连上192.168.10.128

socket基础接口

close关闭socket

调用close函数,会向连接的对应套接字发送EOF (ch04/echo_server.c)

文件结束符:如果 read()调用成功,将返回实际读取的字节数,如果遇到文件结束(EOF)则返回 0,如果出现错误则返回-1。ssize_t 数据类型属于有符号的整数类型,用来存放(读取的)字节数或-1(表示错误)。

终端特殊字符:EOF 是传统模式下的文件结尾字符(通常是 Ctrl-D)。在一行的开始处输入这个字符会导致在终端上读取输入的进程检测到文件结尾的情况(即,read()返回 0)。如果不在一行的开始处,而在其他地方输入这个字符,那么该字符会立刻导致 read()完成调用,返回这一行中目前为止读取到的字符数。在这两种情况下,EOF 字符本身都不会传递给读取的进程。

shutdown优雅的断开TCP连接

1/* Shut down all or part of the connection open on socket FD.
2   HOW determines what to shut down:
3     SHUT_RD   = No more receptions;
4     SHUT_WR   = No more transmissions;
5     SHUT_RDWR = No more receptions or transmissions.
6   Returns 0 on success, -1 for errors.  */
7extern int shutdown (int __fd, int __how) __THROW;

IO复用

1.select接口

 1// echo_client.c
 2#include <stdio.h>
 3#include <stdlib.h>
 4#include <string.h>
 5#include <unistd.h>
 6#include <arpa/inet.h>
 7#include <sys/socket.h>
 8
 9#define BUF_SIZE 1024
10void error_handling(char *message);
11
12int main(int argc, char *argv[])
13{
14    int sock;
15    char message[BUF_SIZE];
16    int str_len;
17    struct sockaddr_in serv_adr;
18
19    if (argc != 3)
20    {
21        printf("Usage : %s <IP> <port>\n", argv[0]);
22        exit(1);
23    }
24
25    sock = socket(PF_INET, SOCK_STREAM, 0);
26    if (sock == -1)
27        error_handling("socket() error");
28
29    memset(&serv_adr, 0, sizeof(serv_adr));
30    serv_adr.sin_family = AF_INET;
31    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
32    serv_adr.sin_port = htons(atoi(argv[2]));
33
34    if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
35        error_handling("connect() error!");
36    else
37        puts("Connected...........");
38
39    while (1)
40    {
41        fputs("Input message(Q to quit): ", stdout);
42        fgets(message, BUF_SIZE, stdin);
43
44        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
45            break;
46
47        write(sock, message, strlen(message));
48        str_len = read(sock, message, BUF_SIZE - 1);
49        message[str_len] = 0;
50        printf("Message from server: %s", message);
51    }
52    close(sock);
53    return 0;
54}
55
56void error_handling(char *message)
57{
58    fputs(message, stderr);
59    fputc('\n', stderr);
60    exit(1);
61}
 1// echo_selectserv.c
 2#include <stdio.h>
 3#include <stdlib.h>
 4#include <string.h>
 5#include <unistd.h>
 6#include <arpa/inet.h>
 7#include <sys/socket.h>
 8#include <sys/time.h>
 9#include <sys/select.h>
10
11#define BUF_SIZE 100
12void error_handling(char *message);
13
14int main(int argc, char *argv[])
15{
16    int serv_sock, clnt_sock;
17    struct sockaddr_in serv_adr, clnt_adr;
18    struct timeval timeout;
19    fd_set reads, cpy_reads;
20
21    socklen_t adr_sz;
22    int fd_max, str_len, fd_num, i;
23    char buf[BUF_SIZE];
24    if (argc != 2)
25    {
26        printf("Usage : %s <port>\n", argv[0]);
27        exit(1);
28    }
29    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
30    memset(&serv_adr, 0, sizeof(serv_adr));
31    serv_adr.sin_family = AF_INET;
32    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
33    serv_adr.sin_port = htons(atoi(argv[1]));
34
35    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
36        error_handling("bind() error");
37    if (listen(serv_sock, 5) == -1)
38        error_handling("listen() error");
39
40    FD_ZERO(&reads);
41    FD_SET(serv_sock, &reads); //注册服务端套接字
42    fd_max = serv_sock;
43
44    while (1)
45    {
46        cpy_reads = reads;
47        timeout.tv_sec = 5;
48        timeout.tv_usec = 5000;
49
50        if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1) //开始监视,每次重新监听
51            break;
52        if (fd_num == 0)
53            continue;
54
55        for (i = 0; i < fd_max + 1; i++)
56        {
57            if (FD_ISSET(i, &cpy_reads)) //查找发生变化的套接字文件描述符
58            {
59                if (i == serv_sock) //如果是服务端套接字时,受理连接请求
60                {
61                    adr_sz = sizeof(clnt_adr);
62                    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
63
64                    FD_SET(clnt_sock, &reads); //注册一个clnt_sock
65                    if (fd_max < clnt_sock)
66                        fd_max = clnt_sock;
67                    printf("Connected client: %d \n", clnt_sock);
68                }
69                else //不是服务端套接字时
70                {
71                    str_len = read(i, buf, BUF_SIZE); //i指的是当前发起请求的客户端
72                    if (str_len == 0)
73                    {
74                        FD_CLR(i, &reads);
75                        close(i);
76                        printf("closed client: %d \n", i);
77                    }
78                    else
79                    {
80                        write(i, buf, str_len);
81                    }
82                }
83            }
84        }
85    }
86    close(serv_sock);
87    return 0;
88}
89
90void error_handling(char *message)
91{
92    fputs(message, stderr);
93    fputc('\n', stderr);
94    exit(1);
95}
1extern int select(int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
2                  struct timeval *__timeout);

2.epoll系列接口

 1// 水平触发 echo_EPLTserv.c
 2#include <stdio.h>
 3#include <stdlib.h>
 4#include <string.h>
 5#include <unistd.h>
 6#include <arpa/inet.h>
 7#include <sys/socket.h>
 8#include <sys/epoll.h>
 9
10#define BUF_SIZE 2
11#define EPOLL_SIZE 50
12void error_handling(char *message);
13
14int main(int argc, char *argv[])
15{
16    int serv_sock, clnt_sock;
17    struct sockaddr_in serv_adr, clnt_adr;
18    socklen_t adr_sz;
19    int str_len, i;
20    char buf[BUF_SIZE];
21
22    struct epoll_event *ep_events;
23    struct epoll_event event;
24    int epfd, event_cnt;
25
26    if (argc != 2)
27    {
28        printf("Usage : %s <port> \n", argv[0]);
29        exit(1);
30    }
31    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
32    memset(&serv_adr, 0, sizeof(serv_adr));
33    serv_adr.sin_family = AF_INET;
34    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
35    serv_adr.sin_port = htons(atoi(argv[1]));
36
37    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
38        error_handling("bind() error");
39    if (listen(serv_sock, 5) == -1)
40        error_handling("listen() error");
41
42    epfd = epoll_create(EPOLL_SIZE); //可以忽略这个参数,填入的参数为操作系统参考
43    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
44
45    event.events = EPOLLIN; //需要读取数据的情况
46    event.data.fd = serv_sock;
47    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); //例程epfd 中添加文件描述符 serv_sock,目的是监听 enevt 中的事件
48
49    while (1)
50    {
51        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //获取改变了的文件描述符,返回数量
52        if (event_cnt == -1)
53        {
54            puts("epoll_wait() error");
55            break;
56        }
57
58        puts("return epoll_wait");
59        for (i = 0; i < event_cnt; i++)
60        {
61            if (ep_events[i].data.fd == serv_sock) //客户端请求连接时
62            {
63                adr_sz = sizeof(clnt_adr);
64                clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
65                event.events = EPOLLIN;
66                event.data.fd = clnt_sock; //把客户端套接字添加进去
67                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
68                printf("connected client : %d \n", clnt_sock);
69            }
70            else //是客户端套接字时
71            {
72                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
73                if (str_len == 0)
74                {
75                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //从epoll中删除套接字
76                    close(ep_events[i].data.fd);
77                    printf("closed client : %d \n", ep_events[i].data.fd);
78                }
79                else
80                {
81                    write(ep_events[i].data.fd, buf, str_len);
82                }
83            }
84        }
85    }
86    close(serv_sock);
87    close(epfd);
88
89    return 0;
90}
91
92void error_handling(char *message)
93{
94    fputs(message, stderr);
95    fputc('\n', stderr);
96    exit(1);
97}
  1// 边沿触发 echo_EPETserv.c
  2#include <stdio.h>
  3#include <stdlib.h>
  4#include <string.h>
  5#include <unistd.h>
  6#include <arpa/inet.h>
  7#include <sys/socket.h>
  8#include <sys/epoll.h>
  9#include <fcntl.h>
 10#include <errno.h>
 11
 12#define BUF_SIZE 4 //缓冲区设置为 4 字节
 13#define EPOLL_SIZE 50
 14void setnonblockingmode(int fd);
 15void error_handling(char *message);
 16
 17int main(int argc, char *argv[])
 18{
 19    int serv_sock, clnt_sock;
 20    struct sockaddr_in serv_adr, clnt_adr;
 21    socklen_t adr_sz;
 22    int str_len, i;
 23    char buf[BUF_SIZE];
 24
 25    struct epoll_event *ep_events;
 26    struct epoll_event event;
 27    int epfd, event_cnt;
 28
 29    if (argc != 2)
 30    {
 31        printf("Usage : %s <port> \n", argv[0]);
 32        exit(1);
 33    }
 34    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
 35    memset(&serv_adr, 0, sizeof(serv_adr));
 36    serv_adr.sin_family = AF_INET;
 37    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
 38    serv_adr.sin_port = htons(atoi(argv[1]));
 39
 40    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
 41        error_handling("bind() error");
 42    if (listen(serv_sock, 5) == -1)
 43        error_handling("listen() error");
 44
 45    epfd = epoll_create(EPOLL_SIZE); //可以忽略这个参数,填入的参数为操作系统参考
 46    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
 47
 48    setnonblockingmode(serv_sock);
 49    event.events = EPOLLIN; //需要读取数据的情况
 50    event.data.fd = serv_sock;
 51    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); //例程epfd 中添加文件描述符 serv_sock,目的是监听 enevt 中的事件
 52
 53    while (1)
 54    {
 55        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //获取改变了的文件描述符,返回数量
 56        if (event_cnt == -1)
 57        {
 58            puts("epoll_wait() error");
 59            break;
 60        }
 61
 62        puts("return epoll_wait");
 63        for (i = 0; i < event_cnt; i++)
 64        {
 65            if (ep_events[i].data.fd == serv_sock) //客户端请求连接时
 66            {
 67                adr_sz = sizeof(clnt_adr);
 68                clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
 69                setnonblockingmode(clnt_sock);    //将 accept 创建的套接字改为非阻塞模式
 70                event.events = EPOLLIN | EPOLLET; //改成边缘触发
 71                event.data.fd = clnt_sock;        //把客户端套接字添加进去
 72                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
 73                printf("connected client : %d \n", clnt_sock);
 74            }
 75            else //是客户端套接字时
 76            {
 77                while (1)
 78                {
 79                    str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
 80                    if (str_len == 0)
 81                    {
 82                        epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //从epoll中删除套接字
 83                        close(ep_events[i].data.fd);
 84                        printf("closed client : %d \n", ep_events[i].data.fd);
 85                        break;
 86                    }
 87                    else if (str_len < 0)
 88                    {
 89                        if (errno == EAGAIN) //read 返回-1 且 errno 值为 EAGAIN ,意味读取了输入缓冲的全部数据
 90                            break;
 91                    }
 92                    else
 93                    {
 94                        write(ep_events[i].data.fd, buf, str_len);
 95                    }
 96                }
 97            }
 98        }
 99    }
100    close(serv_sock);
101    close(epfd);
102
103    return 0;
104}
105
106void error_handling(char *message)
107{
108    fputs(message, stderr);
109    fputc('\n', stderr);
110    exit(1);
111}
112void setnonblockingmode(int fd)
113{
114    int flag = fcntl(fd, F_GETFL, 0);
115    fcntl(fd, F_SETFL, flag | O_NONBLOCK);
116}
 1// size: epoll实例监听的文件描述符数目。Linux2.6.8之后这个参数可以忽略。
 2// retval: epoll实例文件描述符
 3int epoll_create(int size);
 4
 5// op: EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD
 6int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 7
 8// events: 保存发生事件的文件描述符
 9// maxevents: 第二个参数可以保存的最大事件数,即数组大小
10int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
11
12
13typedef union epoll_data
14{
15  void *ptr;
16  int fd;
17  uint32_t u32;
18  uint64_t u64;
19} epoll_data_t;
20
21// events支持的事件类型:
22// EPOLLIN: 需要读取数据的情况
23// EPOLLOUT: 输出缓存为空,可以理解发送数据的情况
24// EPOLLPRI: 收到OOB数据的情况
25// EPOLLRDHUP: 断开连接或半关闭的情况,这在边缘触发方式下非常有用
26// EPOLLERR: 发生错误
27// EPOLLET: 以边沿触发的方式得到事件通知
28// EPOLLONESHOT: 发生一次事件后,相应文件描述符不再收到事件通知。因此需要先epoll_ctl函数的第2个参数传递EPOLL_CTL_MOD再次设置事件。
29struct epoll_event
30{
31  uint32_t events;	/* Epoll events */
32  epoll_data_t data;	/* User data variable */
33};

1.为什么不可以同时设置event.events = EPOLLIN | EPOLLOUT

最主要的是这句:at the time of the callback, epoll has no clue of what happened。大概指的回调的时候,epoll是不能区分出是EPOLLIN还是EPOLLOUT的。换句话说如果你同时监听了EPOLLINEPOLLOUT,当发生了EPOLLIN,由于epoll无法区分种类,那么将会同时监听到EPOLLINEPOLLOUT事件(即使当时没有发生EPOLLOUT)。

2.什么时候设置为EPOLLOUT呢?

当需要发送数据的时候,可以使用epoll_ctl来注册事件,第3个参数可以设置为event->events = EPOLLOUT; event->data.ptr = dataToBeSent,其中dataToBeSent可以为自定义结构struct { int fd; char *buf; }。当发生EPOLLOUT时候表示可以继续写入,直到发送完毕,然后再设置为EPOLLIN

EPOLL讲解、注意点和使用建议 - 简书 (jianshu.com)

epoll默认是水平触发模式。边沿触发ET相比于水平触发LT,改动的地方主要有2处:

  • 设置为fd为非阻塞IO
  • 读取时需要使用while(1)读取,读取完成后read返回-1而且errno==EAGAIN

3.select和epoll的比较

1.select相比epoll的速度慢的原因:

  • 调用select函数后需要遍历所有文件描述符

  • 每次调用select函数时,都需要向该函数传递监视对象信息。

2.select相比epoll的优势

  • epoll只支持Linux平台,而select支持大部分操作系统。因此服务端接入这比较少时适用。