1.1 TCP介绍、编程流程
TCP回顾:
1.面向连接的流式协议,可靠、出错重传、且每收到一个数据都要给出相应的确认;
2.通信之前需要建立链接;
3.服务器是被动链接,客户端是主动链接
TCP与UDP的差异:
TCP C/S架构
TCP编程流程
服务器:
创建套接字socket()
将套接字与服务器网络信息结构体绑定bind()
将套接字设置为监听状态listen()
阻塞等待客户端的连接请求accept()(阻塞函数)
进行通信recv()/send()
关闭套接字close()
客户端:
创建套接字socket()
发送客户端连接请求connect()
进行通信send()/recv()
关闭套接字close()
1.2 TCP编程—socket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#include <sys/types> #include <sys/socket.h> int socket(int domain,int type,int protocol); 功能:创建一个套接字,返回一个文件描述符 参数: domain: 通信域,协议族 AF_UNIX 本地通信 AF_INET ipv4网络协议 AF_INET6 ipv6网络协议 AF_PACKET 底层接口 type: 套接字的类型 SOCK_STREAM 流式套接字(tcp) SOCK_DGRAM 数据报套接字(udp) SOCK_RAW 原始套接字 protocol: 附加协议,如果不需要,则设置为0 返回值: 成功: 文件描述符 失败: -1
案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> int main(int argc, char **argv){ //通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("fail to socket"); exit(1); } return 0; }
1.3 connect、send、recv
1.3.1 connect函数
1
2
3
4
5
6
7
8
9
10
11
12#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd,const struct sockaddr* addr, socklen_t len); 功能: 给服务器发送客户端的连接请求 参数: sockfd: 文件描述符,socket函数的返回值 addr: 要连接的服务器的网络信息结构以(需要自己设置) addrlen: addr的长度 返回值: 成功:0 失败:-1
注意:
1.connect建立连接之后不会产生新的套接字
2.连接成功后才可以开始传输TCP数据
3.头文件:#include<sys/socket.h>
1.3.2 send函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd,const void *buf,size_t len,int flags); 功能: 发送数据 参数: sockfd: 文件描述符 客户端:socket函数的返回值 服务器:accept函数的返回值 buf: 发送的数据 len: buf的长度 flags: 标志位 0 阻塞 MSG_DONTWAIT 非阻塞 返回值: 成功:发送的字节数 失败:-1
注意:
不能用TCP协议发送0长度的数据包
1.3.3 recv函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd,void *buf,size_t len,int flags); 功能: 接收数据 参数: sockfd: 文件描述符 客户端:socket函数的返回值 服务器:accept函数的返回值 buf: 发送的数据 len: buf的长度 flags: 标志位 0 阻塞 MSG_DONTWAIT 非阻塞 返回值: 成功:接收的字节数 失败:-1 如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0
1.3.4 客户端代码
使用windows下的网络调试助手作为服务器

客户端的代码:
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#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet.in.h> #include <string.h> int main(int argc, char **argv){ if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("fail to socket"); exit(1); } //第二步:发送客户端连接请求 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(agrv[2])); if(connect(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { perror("fail to connect"); exit(1); } //第三步:进行通信 //发送数据 char buf[128] = ""; fgets(buf,128,stdin); buf[strlen(buf) - 1] = ''; if(send(sockfd,buf,128,0) == -1){ perror("fail to send"); exit(1); } //接收数据 char text[128] = ""; if(recv(sockfd,text,128,0) == -1){ perror("fail to recv"); exit(1); } printf("from server: %sn",text); //第四步:关闭套接字文件描述符 close(sockfd); return 0; }

1.4 TCP服务器—bind、listen、accept
1.4.1 作为TCP服务器需要具备的条件
1.具备一个可以确知的地址;
2.让操作系统知道是一个服务器,而不是客户端;
3.等待连接的到来
对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始。
1.4.2 bind函数
1
2
3
4
5
6
7
8
9
10
11
12#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen); 功能:将套接字与网络信息结构体绑定 参数: sockfd:文件描述符,socket的返回值 addr: 网络信息结构体 //#include <netinet/in.h> addrlen:addrlen的长度 返回值: 成功: 0 失败: -1
1.4.3 listen函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <sys/types.h> #include <sys/socket.h> int listen(int sockfd,int backlog); 功能: 将套接字由主动修改为被动 使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接 参数: sockfd: socket监听套接字 backlog:连接队列的长度 返回值: 成功:返回0 失败:其他
1.4.4 accept函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen); 功能: 从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞) 参数: sockfd: socket监听套接字 cliaddr:用于存放客户端套接字地址结构 addrlen:套接字地址结构体长度的地址 返回值: 成功:新的文件描述符(只要由客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的) 失败:-1 注意:返回的是一个已连接套接字,这个套接字代表当前这个连接
1.4.5 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
60
61
62
63
64
65
66
67#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> int main(int argc, char **argv) { if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("fail to socket"); exit(1); } //第二步:将套接字与服务器网络信息结构体绑定 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { perror("fail to bind"); exit(1); } //第三步:将套接字设置为被动监听状态 if(listern(sockfd,10) == -1){ perror("fail to listen"); exit(1); } //第四步:阻塞等待客户端的连接请求 int acceptfd; struct sockaddr_in clientaddr; if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){ perror("fail to accept"); exit(1); } //打印连接的客户端信息 printf("ip:%s ,port:%dn",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port)); //第五步:进行通信 char buf[128] = ""; if(recv(acceptfd,buf,128,0) == -1){ perror("fail to recv"); exit(1); } printf("from client: %sn",buf); strcat(buf,"*_*"); if(send(acceptfd,buf,128,0) == -1){ perror("fail to send"); exit(1); } return 0; }
1.5 TCP编程—close、三次握手、四次挥手
1.5.1 close关闭套接字
1.使用close函数即可关闭套接字(关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包)
2.做服务器时
1>关闭监听套接字将导致服务器无法接收到新的连接,但不会影响已经建立的连接
2>关闭accept返回的已连接套接字将导致他所代表的连接被关闭,但不会影响服务器的监听
3.做客户端时
关闭连接就是关闭连接,不意味着其他
1.5.2 三次握手

1.5.3 四次挥手

1.6 TCP并发服务器
TCP原本不是并发服务器,TCP服务器同一时间只能与一个客户端通信。
原始代码:
client.c
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#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet.in.h> #include <string.h> int main(int argc, char **argv){ if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("fail to socket"); exit(1); } //第二步:发送客户端连接请求 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(agrv[2])); if(connect(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { perror("fail to connect"); exit(1); } //第三步:进行通信 //发送数据 char buf[128] = ""; while(1){ fgets(buf,128,stdin); buf[strlen(buf) - 1] = ''; if(send(sockfd,buf,128,0) == -1){ perror("fail to send"); exit(1); } if(strncmp(buf,"quit",4) == 0){ exit(0); } //接收数据 char text[128] = ""; if(recv(sockfd,text,128,0) == -1){ perror("fail to recv"); exit(1); } printf("from server: %sn",text); } //第四步:关闭套接字文件描述符 close(sockfd); return 0; }
server.c
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#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> int main(int argc, char **argv) { if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("fail to socket"); exit(1); } //将套接字设置为允许重复使用本机地址或者设置为端口复用 int on = 1; if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){ perror("fail to setsockopt"); exit(1); } //第二步:将套接字与服务器网络信息结构体绑定 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { perror("fail to bind"); exit(1); } //第三步:将套接字设置为被动监听状态 if(listern(sockfd,10) == -1){ perror("fail to listen"); exit(1); } //第四步:阻塞等待客户端的连接请求 int acceptfd; struct sockaddr_in clientaddr; if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){ perror("fail to accept"); exit(1); } //打印连接的客户端信息 printf("ip:%s ,port:%dn",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port)); //第五步:进行通信 char buf[128] = ""; ssize_t bytes; while(1){ if(bytes = recv(acceptfd,buf,128,0) < 0){ perror("fail to recv"); exit(1); } else if(bytes == 0){ printf("the clinet quitedn"); exiy(1); } if(strncmp(buf,"quit",4) == 0){ exit(0); } printf("from client: %sn",buf); strcat(buf,"*_*"); if(send(acceptfd,buf,128,0) == -1){ perror("fail to send"); exit(1); } } return 0; }

TCP不能实现并发的原因:
由于TCP服务器端有两个读阻塞函数,accept和recv,两个函数需要先后运行,所以导致运行一个函数的时候另一个函数无法执行,所以无法保证一边连接客户端,一边与其他客户端通信。
如何实现TCP并发服务器:
1.使用多进程实现TCP并发服务器
2.使用多线程实现TCP并发服务器
1.6.1 多进程实现并发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20int sockfd = socket(); bind() listen() while(1) { accept() pid = fork(); if(pid > 0) { } else if(pid == 0) { while(1) { recv()/send() } } }
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
99
100
101
102
103
104
105
106
107
108
109#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <signal.h> #include <sys/wait.h> //使用多进程实现TCP并发服务器 #define N 128 #define ERR_LOG(errmsg)do{ perror(errmsg); exit(1); }while(0) void handler(int sig){ wait(NULL); } int main(int argc, char **argv) { if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ ERR_LOG("fail to socket"); } //将套接字设置为允许重复使用本机地址或者设置为端口复用 int on = 1; if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){ ERR_LOG("fail to setsockopt"); } //第二步:将套接字与服务器网络信息结构体绑定 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { ERR_LOG("fail to bind"); } //第三步:将套接字设置为被动监听状态 if(listern(sockfd,10) == -1){ perror("fail to listen"); exit(1); } //使用信号,异步的方式处理僵尸进程 signal(SIGCHID,handler) while(1){ //第四步:阻塞等待客户端的连接请求 int acceptfd; struct sockaddr_in clientaddr; if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){ perror("fail to accept"); exit(1); } //打印连接的客户端信息 printf("ip:%s ,port:%dn",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port)); //第五步:使用fork函数创建子进程,父进程继续负责连接,子进程负责与客户端通信 pid_t pid; if((pid = fork()) < 0){ ERR_LOG("fail to fork"); } else if(pid > 0){ //父进程负责执行accept,所以if语句结束后继续在accept函数的位置阻塞 } else{ //子进程负责和指定的客户端通信 char buf[128] = ""; ssize_t bytes; while(1){ if(bytes = recv(acceptfd,buf,128,0) < 0){ perror("fail to recv"); exit(1); } else if(bytes == 0){ printf("the clinet quitedn"); exit(1); } if(strncmp(buf,"quit",4) == 0){ exit(0); } printf("from client: %sn",buf); strcat(buf,"*_*"); if(send(acceptfd,buf,128,0) == -1){ perror("fail to send"); exit(1); } } } } return 0; }

1.6.2 多线程并发实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void *thread_fun(void *arg){ while(1){ recv()/send() } } sockfd = socket() bind() listen() while(1) { accept() //只要有客户端连接上,则创建一个子线程与之通信 pthread_create(&thread,NULL,thread_fun,...) pthread_detach(); }
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <signal.h> #include <sys/wait.h> //使用多进程实现TCP并发服务器 #define N 128 #define ERR_LOG(errmsg)do{ perror(errmsg); exit(1); }while(0) typedef struct{ struct sockaddr_in addr; int acceptfd; }MSG; void *pthread_fun(void *arg){ char buf[N] = ""; ssize_t bytes; MSG msg = *(MSG *)arg; while(1){ if(bytes = recv(msg.acceptfd,buf,128,0) < 0){ perror("fail to recv"); exit(1); } else if(bytes == 0){ printf("the clinet quitedn"); exit(1); } if(strncmp(buf,"quit",4) == 0){ printf("The client quitedn"); pthread_exit(NULL); } printf("[%s - %d]: %sn",inet_ntoa(msg.addr.sin_addr),ntohs(msg.addr.sin_port),buf); strcat(buf,"*_*"); if(send(msg.acceptfd,buf,128,0) == -1){ perror("fail to send"); exit(1); } } } int main(int argc, char **argv) { if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ ERR_LOG("fail to socket"); } //将套接字设置为允许重复使用本机地址或者设置为端口复用 int on = 1; if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){ ERR_LOG("fail to setsockopt"); } //第二步:将套接字与服务器网络信息结构体绑定 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { ERR_LOG("fail to bind"); } //第三步:将套接字设置为被动监听状态 if(listern(sockfd,10) == -1){ perror("fail to listen"); exit(1); } //使用信号,异步的方式处理僵尸进程 signal(SIGCHID,handler) while(1){ //第四步:阻塞等待客户端的连接请求 int acceptfd; struct sockaddr_in clientaddr; if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){ perror("fail to accept"); exit(1); } //打印连接的客户端信息 printf("ip:%s ,port:%dn",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port)); //创建子线程与客户端进行通信 MSG msg; msg.addr = clientaddr; msg.acceptfd = acceptfd; pthread_t thread; if(pthread_create(&thread,NULL,pthread_fun,&msg) != 0){ ERR_LOG("fail to pthread_create"); } pthread_detach(thread); } } return 0; }

1.7 Web服务器介绍
1.7.1 web服务器简介
Web服务器又称WWW服务器、网站服务器等
特点:
使用HTTP协议与客户机浏览器进行信息交流
不仅能存储信息,还能在用户通过web浏览器提供的信息的基础上运行脚本和程序
该服务器可安装在UNIX、Linux或Windows等操作系统上
著名的服务器有Apache、Tomcat、IIS等
1.7.2 HTTP协议
Webserver—HTTP协议(超文本协议)
概念:一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
特点:
1.支持C/S架构
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径,常用方法:GET、POST
3.无连接:限制每次连接只处理一个请求
1.7.3 Webserver通信过程

1.7.4 Web编程开发
网页浏览(使用过GET方式)
客户端浏览请求

Web服务器的ip地址是192.168.3.103,端口号是9999,要访问的网页时about.html

服务器收到的数据:
1
2
3
4
5
6
7GET/index.html HTTP/1.1 Accept:image/gif.image/jpeg,*/* Accept-Language:zh-cn Connection:Keep-Alive Host:localhost Accept-Encoding:gzip,deflate
服务器应答的格式:
服务器接收到浏览器发送的数据之后,需要判断GET/后面跟的网页是否存在,如果存在则请求成功,发送指定的指令,并发送文件内容给浏览器,如果不存在,则发送请求失败的指令
请求成功
1
2
3
4"HTTP/1.1 200 OKrn" "Content-Type: text/htmlrn" "rn";
请求失败
1
2
3
4
5
6"HTTP/1.1 404 Not Foundrn" "Content-Type: text/htmlrn" "rn" "<HTML><BODY>File not found</BODY></HTML>"
案例:
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <signal.h> #include <sys/wait.h> //使用多进程实现TCP并发服务器 #define N 128 #define ERR_LOG(errmsg)do{ perror(errmsg); exit(1); }while(0) void *pthread_fun(void *arg){ int acceptfd = *(int *)arg; char buf[N] = ""; char head[] = "HTTP/1.1 200 OKrn" "Content-Type: text/htmlrn" "rn"; char err[] = "HTTP/1.1 404 Not Foundrn" "Content-Type: text/htmlrn" "rn" "<HTML><BODY>File not found</BODY></HTML>"; //接收浏览器通过http协议发送的数据包 if(recv(acceptfd,buf,N,0) < 0){ ERR_LOG("fail to recv"); } printf("******************nn"); printf("%sn",buf); //通过获取的数据包中得到浏览器要访问的网络文件名 // GET /about.html http/1.1 char filename[128] = ""; sscanf(buf,"GET /%s",filename); //sscanf函数遇空格结束,所以直接可以获取文件名 if(strncmp(filename,"HTTP/1.1",strlen("http/1.1")) == 0){ strcpy(filename,"about.html"); } printf("filename = %sn",filename); char path[128] = "./sqlite/"; strcat(path,filename); //通过解析出来的网页文件名,查找本地中有没有这个文件 int fd; if((fd = open(path,O_RDONLY)) < 0){ //如果文件不存在,则发送不存在对应的指令 if(errno == ENOENT) { if(send(acceptfd,err,strlen(err),0) < 0){ ERR_LOG("fail to send"); } close(acceptfd); pthread_exit(NULL); } else{ ERR_LOG("fail to open"); } } //如果文件存在,先发送指令告知浏览器 if(send(acceptfd,head,strlen(head),0) < 0){ ERR_LOG("fail to send"); } //读取网页文件中的内容并发送给浏览器 ssize_t bytes; char text[1024] = ""; while((bytes = read(fd,text,1024)) > 0){ if(send(acceptfd,text,bytes,0) < 0){ ERR_LOG("fail to send"); } } pthread_exit(NULL); } int main(int argc, char **argv) { if(argc < 3){ fprintf(stderr,"UseageL %s [ip] [port]n",argv[0]); } //第一步:通过socket函数创建一个TCP套接字 int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ ERR_LOG("fail to socket"); } //将套接字设置为允许重复使用本机地址或者设置为端口复用 int on = 1; if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){ ERR_LOG("fail to setsockopt"); } //第二步:将套接字与服务器网络信息结构体绑定 struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1) { ERR_LOG("fail to bind"); } //第三步:将套接字设置为被动监听状态 if(listern(sockfd,10) == -1){ perror("fail to listen"); exit(1); } //使用信号,异步的方式处理僵尸进程 signal(SIGCHID,handler) while(1){ //第四步:阻塞等待客户端的连接请求 int acceptfd; struct sockaddr_in clientaddr; if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){ perror("fail to accept"); exit(1); } //打印连接的客户端信息 printf("ip:%s ,port:%dn",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port)); //创建子线程与客户端进行通信 MSG msg; msg.addr = clientaddr; msg.acceptfd = acceptfd; pthread_t thread; if(pthread_create(&thread,NULL,pthread_fun,&msg) != 0){ ERR_LOG("fail to pthread_create"); } pthread_detach(thread); } } return 0; }

最后
以上就是外向煎饼最近收集整理的关于TCP编程详解的全部内容,更多相关TCP编程详解内容请搜索靠谱客的其他文章。
发表评论 取消回复