C/C++ WebServer 4 Linux 网络编程

Meqt

网络结构模式

C/S 结构
MAC地址、IP地址、端口
MAC地址48位,OSI第二层(数据链路层)
网卡(以太网、无线网)功能:

  1. 数据的封装与解封装
  2. 链路管理
  3. 数据编码与译码

MAC:
前三个字节:制造商
后三个字节:制造商的某个产品
ifconfig

IP
A
1.0.0.1
126.255.255.254
10.(私有地址)
B
128.0.0.1
191.255.255.254
172.16~172.31
C
192.0.0.1
233.255.255.254
192.168.

网络IP + 主机IP
网络地址,广播地址。
A类126个
B类16384个网络
127.* 测试回路
子网掩码

Port
标识进程,一个应用程序可有多个进程
虚拟端口 物理端口(接口)
65535个
0-1023 周知端口
1024-49151:注册端口
私有/动态端口
4.4 网络模型
OSI7层网络模型

  • 应用层 应用程序
  • 表示层 数据的表示、安全、压缩
  • 会话层 建立、管理和连接会话
  • 传输层 建立、管理和维护端到端的链接,传输数据的协议、端口号,TCP/UDP 段
  • 网络层 IP选址和路由选择
  • 数据链路层 建立逻辑连接,介质访问和链路管理LLC+MAC,差错检验,帧
  • 物理层 比特
    TCP/IP协议族
    应用层:
    传输层:TCP、UDP
    网络层:IP,ICMP,IGMP
    网络接口层:ARP,RARP
    上层协议使用下层协议的服务

协议

协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组約定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。为了使数据在网络上从源到达目的,网络通信的参与方必须遵循相同的规则,这套规则称为协议。

1
2
netstat
ps aux | grep ssh

UDP:

16位端口号,16位目的端口号
16位UDP长度,16位校验和

TCP:

16位元端口,16位目的端口
32位序号
32位确认号
URG,ACK,PSH,RST,SYN,FIN;16位窗口大小
16位校验和,16位紧急指针

IP:

16位总字节数
16位标识,3位标识,13位偏移
8位TTL,8位协议(上层的协议)
网络通信的过程
封装,分用(解析下层协议中的头来传送给对应的上层协议)

1
arp -a 

socket 介绍

文件描述符,读写缓冲区
服务器端
客户端

字节序

Big-Endian: 最高位在低地址处
Little-Endian:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// byteorder.c
#include <stdio.h>

int main() {
union {
short value;
char bytes[sizeof(short)];
}test;

test.value = 0x0102;
if ( (test.bytes[0]==1) && (test.bytes[1]==2) ) {
printf("Big-Endian");
}else printf("Little-Endian");
return 0;
}

字节序转换函数

网络字节序:Big-Endian

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <arpa/inet.h>
uint16_t htons (uint16_t hostshort);
uint16_t ntohs (uint16_t netshort);
uint32_t htonl (uint32_t hostlong);
uint32_t ntohl (uint32_t netlong);

#include <stdio.h>
#include <arpa/inet.h>
int main() {
// htons
unsigned short a = 0x0102;
printf(": %x\n", a); //102
unsigned short b = htons(a);
printf("b : %x\n", b); //201
return 0;

//htonl 裝換IP
char buf [4] = {192, 168, 1, 100);
int num = *(int *)buf;
int sum = hton1 (num);
unsigned char *p = (char *)&sum;
34
35
printf("%d %d %d %d\n", *p, * (p+1), * (p+2), *(p+3));// 100 1 168 192
}

socket 地址

IP 地址转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);

// iptrans.c
#include <stdio.h>
#include <arpa/inet.h>

int main() {
char buf[] = "192.168.10.14";
unsigned int num = 0;
inet_pton(AF_INET, buf, &num);
unsigned char *p = (unsigned char*)&num;
printf("%d.%d.%d.%d\n", p[0], p[1], p[2], p[3]);
char ip[16];
const char* str = inet_ntop(AF_INET, &num, ip, sizeof(ip));
printf("%s\n", str);
return 0;
}

TCP 通信流程

UDP: 无连接,单播、多播、广播,不可靠
TCP:面向连接的,可靠,面向字节流,仅支持单播传输
服务器端

  1. 建立用于链接的监听套接字(文件描述符)
  2. 将监听的文件描述符和本地的IP和端口绑定
  3. 设置监听
  4. 阻塞等待
  5. 通信
  6. 通信结束,断开连接
    客户端:
  7. 创建用于通信的套接字
  8. 连接服务器,指定IP和port
  9. 通信
  10. 通信结束,断开连接

socket 函数

1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h 
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头交件,上面两个就可以省旺

int socket (int domain, int type, int protocol); // create socket domain: protocal family,
int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen);
int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn
int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t write(int fd, const void *buf, size_t count) ;
ssize_t read(int fd, void *buf, size_t count);

TCP通信实现(服务器端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;

// inet_pton(AF_INET, "127.0.0.1", saddr.sin_addr.s_addr);
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(9999);
int ret = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret==-1) {
perror("bind");
exit(0);
}

ret = listen(lfd, 8);
if (ret==-1) {
perror("listen");
exit(0);
}

struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int cfd = accept(lfd, (struct sockaddr*)&clientaddr, &len);
if (cfd==-1) {
perror("accept");
exit(0);
}

char clientIP[16];
inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
unsigned short clientPort = ntohs(clientaddr.sin_port);
printf("client ip is %s, port:%d\n", clientIP, clientPort);

while(1) {
char recvBuf[1024] = {0};
int recvlen = read(cfd, recvBuf, sizeof(recvBuf));
if (recvlen==-1) {
perror("read");
exit(-1);
}else if (recvlen>0) {
printf("received: %s\n", recvBuf);
}else {
printf("connection closed\n");
break;
}
char *sstr = "Hello, I'm server.";
write(cfd, sstr, strlen(sstr));
}

close(cfd);
close(lfd);
return 0;
}

TCP通信实现(客户端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>

int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in clientSockerAddr;
inet_pton(AF_INET, "127.0.0.1", &clientSockerAddr.sin_addr.s_addr);
clientSockerAddr.sin_port = htons(9999);
clientSockerAddr.sin_family = AF_INET;
int ret = connect(fd, (struct sockaddr*)&clientSockerAddr, sizeof(clientSockerAddr));
if (ret == -1) {
perror("connect");
exit(0);
}

char *data = "Hello, I'm cllient.";
write(fd, data, strlen(data));

char recvBuf[1024] = {0};
int recvlen = read(fd, recvBuf, sizeof(recvBuf));
if (recvlen==-1) {
perror("read");
exit(-1);
}else if (recvlen>0) {
printf("received message from server: %s\n", recvBuf);
}else {
printf("server closed\n");
}

close(fd);
return 0;
}

TCP三次握手

滑动窗口

TCP四次挥手

多进程实现并发服务器(1)

一个父进程,多个子进程
父进程:等待连接
子进程:完成通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// server_process.c (1)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd==-1) {
perror("socket");
exit(0);
}

struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(9999);
serverAddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if (ret==-1) {
perror("bind");
exit(-1);
}

ret = listen(lfd, 8);
if (ret==-1) {
perror("listen");
exit(0);
}

while(1) {
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
if (cfd==-1) {
perror("accept");
exit(0);
}

//child process for communication
pid_t pid = fork();
if (pid==0) {
char ip[16] = {0};
inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip));
unsigned short port = ntohs(clientAddr.sin_port);
printf("client ip:%s, port:%d\n", ip, port);

//MARK: communication
char recvBuf[1024];
while(1) {
int len = read(cfd, recvBuf, sizeof(recvBuf));
if (len==-1) {
perror("read");
exit(0);
}else if (len==0) {
printf("client closed\n");
}else{
printf("server received message:%s\n", recvBuf);
}
write(cfd, recvBuf, strlen(recvBuf));
}
close(cfd);
exit(0);
}
}
close(lfd);
return 0;
}

多进程实现并发服务器(2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// server_process.c (2)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>

void recycleChild(int arg) {
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if (ret==-1) {
break;
}else if (ret==0) {
break;
}else{
printf("child %d was recyled\n", ret);
}
}
}
int main() {

struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = recycleChild;
//TODO: register sigchild signal
sigaction(SIGCHLD, &act, NULL);

int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd==-1) {
perror("socket");
exit(0);
}

struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(9999);
serverAddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if (ret==-1) {
perror("bind");
exit(-1);
}

ret = listen(lfd, 8);
if (ret==-1) {
perror("listen");
exit(0);
}

while(1) {
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
if (cfd==-1) {
if (errno == EINTR) continue;
else{
perror("accept");
exit(0);
}
}

//child process for communication
pid_t pid = fork();
if (pid==0) {
char ip[16] = {0};
inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip));
unsigned short port = ntohs(clientAddr.sin_port);
printf("client ip:%s, port:%d\n", ip, port);

//MARK: communication
char recvBuf[1024];
while(1) {
int len = read(cfd, recvBuf, sizeof(recvBuf));
if (len==-1) {
perror("read");
exit(0);
}else if (len==0) {
printf("client closed\n");
}else{
printf("server received message:%s\n", recvBuf);
}
write(cfd, recvBuf, strlen(recvBuf));
}
close(cfd);
exit(0);
}
}
close(lfd);
return 0;
}

多线程实现并发服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// server_thread.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>

struct sockInfo {
int fd; //file describor
pthread_t tid;
struct sockaddr_in addr;
};

struct sockInfo sockinfos[128];
void *working(void* arg) {
struct sockInfo *pinfo = (struct sockInfo*)arg;
char ip[16] = {0};
inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip));
unsigned short port = ntohs(pinfo->addr.sin_port);
printf("client ip:%s, port:%d\n", ip, port);

//MARK: communication
char recvBuf[1024];
while(1) {
int len = read(pinfo->fd, recvBuf, sizeof(recvBuf));
if (len==-1) {
perror("read");
exit(0);
}else if (len==0) {
printf("client closed\n");
break;
}else{
printf("server received message:%s\n", recvBuf);
}
write(pinfo->fd, recvBuf, strlen(recvBuf));
}
close(pinfo->fd);
return NULL;
}
int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd==-1) {
perror("socket");
exit(0);
}

struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(9999);
serverAddr.sin_addr.s_addr = INADDR_ANY;

//MARK: binding
int ret = bind(lfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if (ret==-1) {
perror("bind");
exit(-1);
}

//MARK: Listen
ret = listen(lfd, 128);
if (ret==-1) {
perror("listen");
exit(0);
}

//MARK: Initialize sockinfos
int max = sizeof(sockinfos)/sizeof(sockinfos[0]);
for (int i=0; i<max; i++) {
bzero(&sockinfos[i], sizeof(sockinfos[i]));
sockinfos[i].fd = -1;
sockinfos[i].tid = -1;
}

while(1) {
//MARK child thread processing child addr
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
struct sockInfo *pinfo;
for (int i=0; i<max; i++) {
if (sockinfos[i].fd == -1) {
pinfo = &sockinfos[i];
break;
}
if (i==max-1) {
sleep(1);
i--;
}
}

pinfo->fd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
memcpy(&pinfo->addr, &clientAddr, sizeof(clientAddr));
pthread_create(&pinfo->tid, NULL, working, (void*)pinfo);
pthread_detach(pinfo->tid);
}
close(lfd);
return 0;
}

TCP状态转换

半关闭、端口复用

1
2
3
4
netstat
-a all
-p 正在使用socket的程序的名称
-n 直接显示ipaddress

端口复用最常用的用途是:

  • 防止服务器重启时之前绑定的端口还未释放(TIME_WAIT)
  • 程序突然退出而系统没有释放端口
    1
    2
    3
    4
    5
    #include <sys/socket.h>
    int
    setsockopt(int socket, int level, int option_name,
    const void *option_value, socklen_t option_len);
    // should be called before binding

IO多路复用简介

IO多路复用/IO多路转接
IO多路复用使得程序能同时监控多个文件描述符,能够提高程序的性能,Linux下实现 I/0 多路复用的系统调用主要有 select、poll 和epoll.
阻塞等待,BIO模型
好处:不占CPU时间片
缺点:同一时刻只能处理一个操作
多线程或多进程
缺点:线程/进程调度消耗CPU资源
每个线程/进程一个Client
非阻塞,忙轮询,NIO模型
优点:提高了程序的执行效率
缺点:需要占用更多的CPU和系统资源
IO多路转接技术

select API介绍

主旨思想

  1. 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
  2. 调用一个系统逐数,监听该列表中的文件玉达行,直到这些街达符中的一个或者多个进行1/0操作时,该函数才返回。
  3. 这个函数是阻塞
  4. 函数对文件描述符的检测的操作是由内核完成的
  5. 在返回时,它会告诉进程有多少(哪些) 描述符要进行I/0操作。
1
2
3
4
5
6
7
8
9
/**
@param writefds if == 1, 可以写入数据
*/
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

缺点:

  1. 每次调用select,都需要把fd集合从用广态拷贝到内核态,这个开销在fd很多时会很大
  2. 同时每次调用select都需妥在内核遍历传递进来的所有fd,这个开销在fa很多时也很大
  3. sclect支持的文件描述符数量太小了,默认是1024
  4. fdls集合不能重用,每次都需要重置

select代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// select.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>

int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;

// bind
bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));

//listen
listen(lfd, 8);
fd_set rdset, tmp;
FD_ZERO(&rdset);
FD_SET(lfd, &rdset);
int maxfd = lfd;
while(1) {
tmp = rdset;
// cal select api
int ret = select(maxfd+1, &tmp, NULL, NULL, NULL); // last NULL referst to block
if (ret==-1){
perror("select");
exit(-1);
}else if (ret==0) {
continue;
}else{
if (FD_ISSET(lfd, &tmp)) {
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
FD_SET(cfd, &rdset);
maxfd = maxfd > cfd ? maxfd : cfd;
}

for (int i=lfd+1; i<=maxfd; i++) {
if (FD_ISSET(i, &tmp)) {
// corespond cfd has data send in
char buf[1024] = {0};
int len = read(i, buf, sizeof(buf));
if (len==-1) {
perror("read");
exit(-1);
}else if (len==0) {
printf("client closed.\n");
close(i);
FD_CLR(i, &rdset);
}else {
printf("read: %s\n", buf);
write(i, buf, strlen(buf)+1);
}
}
}
}
}
close(lfd);
return 0;
}

poll API介绍及代码编写

1
2
3
4
5
6
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// poll.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <unistd.h>
#include <string.h>

int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;

// bind
bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));

//listen
listen(lfd, 8);
struct pollfd fds[1024];
for (int i=0; i<1024; i++) {
fds[i].fd = -1;
fds[i].events = POLLIN;
}
fds[0].fd = lfd;
int nfds = 0;
while(1) {
// cal select api
int ret = poll(fds, nfds+1, -1); // last NULL referst to block
if (ret==-1){
perror("poll");
exit(-1);
}else if (ret==0) {
continue;
}else{
if (fds[0].revents & POLLIN ) {
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
for (int i=1; i<1024; i++) {
if(fds[i].fd ==-1 ){
fds[i].fd = cfd;
fds[i].events = POLLIN;
nfds = nfds > cfd ? nfds: cfd;
break;
}
}
}

for (int i=1; i<=nfds; i++) {
if (fds[i].revents & POLLIN == POLLIN) {
// corespond cfd has data send in
char buf[1024] = {0};
int len = read(fds[i].fd, buf, sizeof(buf));
if (len==-1) {
perror("read");
exit(-1);
}else if (len==0) {
printf("client closed.\n");
close(fds[i].fd);
fds[i].fd = -1;
}else {
printf("read: %s\n", buf);
write(fds[i].fd, buf, strlen(buf)+1);
}
}
}
}
}
close(lfd);
return 0;
}

epoll API介绍

Epoll

1
2
3
4
struct eventpoll{
struct rb_root rbr;
struct list_head rdlist;
}

epoll 代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// epoll.c
#include <stdio.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
//create socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;

// bind
bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));

//listen
listen(lfd, 8);

int epfd = epoll_create(100);
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

struct epoll_event epevs[1024];
while(1) {
int ret = epoll_wait(epfd, epevs, 1024, -1);
if (ret==-1) {
perror("epoll_wait");
exit(-1);
}

printf("ret = %d\n", ret);
for (int i=0; i<ret; i++) {
if (epevs[i].data.fd == lfd) {
// client connection
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
}else{
int cfd = epevs[i].data.fd;
char buf[1024] = {0};
int len = read(cfd, buf, sizeof(buf));
if (len==-1) {
perror("read");
exit(-1);
}else if (len==0) {
printf("client closed.\n");
close(cfd);
epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
}else {
printf("read: %s\n", buf);
write(cfd, buf, strlen(buf)+1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}

epoll的两种工作模式

LT level-triggered 水平触发(缺省)
ET edge-triggered 边沿触发,或高速触发模式,只支持非阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// epoll_et.c
#include <stdio.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main() {
//create socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;

// bind
bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));

//listen
listen(lfd, 8);

int epfd = epoll_create(100);
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

struct epoll_event epevs[1024];
while(1) {
int ret = epoll_wait(epfd, epevs, 1024, -1);
if (ret==-1) {
perror("epoll_wait");
exit(-1);
}

printf("ret = %d\n", ret);
for (int i=0; i<ret; i++) {
if (epevs[i].data.fd == lfd) {
// client connection
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
int flag = fcntl(cfd, F_GETFL);
fcntl(cfd, F_SETFL, flag | O_NONBLOCK);
epev.events = EPOLLIN | EPOLLET;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
}else{
int cfd = epevs[i].data.fd;
char buf[5] = {0};
int len;
while((len = read(cfd, buf, sizeof(buf)))>0) {
printf("receive data: %s\n", buf);
write(cfd, buf, strlen(buf));
}
if (len==0) {
printf("client closed.\n");
}else if(len==-1) {
perror("read");
exit(-1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// client.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

// 创建socket
int fd = socket(PF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
return -1;
}

struct sockaddr_in seraddr;
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(9999);

// 连接服务器
int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

if(ret == -1){
perror("connect");
return -1;
}

while(1) {
char sendBuf[1024] = {0};
fgets(sendBuf, sizeof(sendBuf), stdin);

write(fd, sendBuf, strlen(sendBuf) + 1);

// 接收
int len = read(fd, sendBuf, sizeof(sendBuf));
if(len == -1) {
perror("read");
return -1;
}else if(len > 0) {
printf("read buf = %s\n", sendBuf);
} else {
printf("服务器已经断开连接...\n");
break;
}
}
close(fd);
return 0;
}

UDP通信实现

1
2
3
4
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// upd_client.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd==-1) {
perror("socket");
exit(-1);
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);

int num = 0;
while(1) {
char sendBuf[1024] = {0};
sprintf(sendBuf, "Hello I'm client, %d\n", num++);
sendto(fd, sendBuf, strlen(sendBuf), 0, (struct sockaddr *)&saddr, sizeof(saddr));
sleep(1);
}
close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// udp_server.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd==-1) {
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;
bind(fd, (struct sockaddr*)&addr, sizeof(addr));

while(1) {
char buf[1024];
char ipbuf[16];
struct sockaddr_in clientAddr;
int len = sizeof(clientAddr);
int num = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&clientAddr, &len);
if (num==-1) {
perror("recvform");
exit(-1);
}
inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf));
printf("client IP:%s, port:%d\n", ipbuf, ntohs(clientAddr.sin_port) );
printf("client say: %s\n", buf);
sendto(fd, buf, strlen(buf), 0, (struct sockaddr*)&clientAddr, sizeof(clientAddr));
}
close(fd);
return 0;
}

广播

组播

多播通信必须依赖于IP多播地址,在IPV4 中它的范国从 224.0.0.0到 239.255.255.255,并被划分肩部链接多播地址、预留多播地址和管理杈限多播地址三类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// multi_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main() {

// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}

struct in_addr in;

// 2.客户端绑定本地的IP和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;

int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}

// Add membership
struct ip_mreq op;
inet_pton(AF_INET, "239.0.0.10", &op.imr_multiaddr.s_addr );
op.imr_interface.s_addr = INADDR_ANY;
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof(op));

// 3.通信
while(1) {

char buf[128];
// 接收数据
int num = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("server say : %s\n", buf);

}

close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// multi_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main() {

// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}

// 2.set multicast
struct in_addr imr_multiaddr;
inet_pton(AF_INET, "239.0.0.10", &imr_multiaddr.s_addr);
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof(imr_multiaddr));

// 创建一个广播的地址
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(9999);
inet_pton(AF_INET, "239.0.0.10", &cliaddr.sin_addr.s_addr);

// Communication
int num = 0;
while(1) {
char sendBuf[128];
sprintf(sendBuf, "hello, client....%d\n", num++);
// 发送数据
sendto(fd, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
printf("Multicasting data: %s\n", sendBuf);
sleep(1);
}

close(fd);
return 0;
}

本地套接字通信

本地的进程间通信
服务器端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//  ipc_server.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/un.h>

int main(int argc, const char * argv[]) {
// insert code here...
int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket");
exit(-1);
}

struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
unlink("server.sock");
strcpy(addr.sun_path, "server.sock");
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if (ret==-1) {
perror("bind");
exit(-1);
}

ret = listen(lfd, 100);
if (ret==-1) {
perror("listen");
exit(-1);
}

struct sockaddr_un clientAddr;
socklen_t len = sizeof(clientAddr);
int cfd = accept(lfd, (struct sockaddr*)&clientAddr, &len);
if (cfd==-1) {
perror("accept");
exit(-1);
}
printf("client socket filename: %s\n", clientAddr.sun_path );
while (1) {
char buf[1024] = {0};
int len = recv(cfd, buf, sizeof(buf), 0);
if (len==-1) {
perror("recv");
exit(-1);
}else if(len==0) {
printf("client closed...");
break;
}else{
printf("cliend say: %s\n", buf);
send(cfd, buf, len, 0);
}
}

close(cfd);
close(lfd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//  ipc_client.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/un.h>

int main(int argc, const char * argv[]) {
// insert code here...
int cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (cfd == -1) {
perror("socket");
exit(-1);
}

struct sockaddr_un clientAddr;
clientAddr.sun_family = AF_LOCAL;
unlink("client.sock");
strcpy(clientAddr.sun_path, "client.sock");
int ret = bind(cfd, (struct sockaddr*)&clientAddr, sizeof(clientAddr));
if (ret==-1) {
perror("bind");
exit(-1);
}

struct sockaddr_un serverAddr;
strcpy(serverAddr.sun_path, "server.sock");
ret = connect(cfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));

// communication
int num = 0;

while (1) {
char buf[1024] = {0};
sprintf(buf, "hello, I'm client num:%d", num++);
send(cfd, buf, strlen(buf), 0);

int len = recv(cfd, buf, sizeof(buf), 0);
if (len==-1) {
perror("recv");
exit(-1);
}else if(len==0) {
printf("connection closed...");
break;
}else{
printf("server say: %s\n", buf);
}
sleep(1);
}

close(cfd);
return 0;
}
  • Post title:C/C++ WebServer 4 Linux 网络编程
  • Post author:Meqt
  • Create time:2022-12-17 19:40:08
  • Post link:https://meqtmac.github.io/2022/12/17/WebServerNote/webserver-note4/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.