13_tcp
启动新的线程或进程 服务器客户端(并发服务器)2、创建TCP套接字1 int sockfd = socket(
AF_INET, SOCK_STREAM, 0);socket函数创建的TCP套接字,没有端口,默认为主动连接
特性3、调用connect函数连接服务器tcp客户端通信之前 必须事先 建立和服务器的连接1 #include
<sys/types.h> /* See NOTES */2 #include <sys/socket.h>
3 int connect(int sockfd, const struct sockaddr *addr,s
ocklen_t addrlen);addr地址结构体 存放的是服务器的IP、PORT返回值:成功为0 失败-
11 //连接服务器2 struct sockaddr_in ser_addr;3 bzero(&ser_ad
dr, sizeof(ser_addr));4 ser_addr.sin_family = AF_INET;5
ser_addr.sin_port = htons(8000);6 ser_addr.sin_addr.s_
addr = inet_addr("10.9.11.251");7 connect(sockfd, (stru
ct sockaddr *)&ser_addr, sizeof(ser_addr));如果sockfd没有固定
端口 在调用connect时系统自动分配随机端口为源端口4、send发送消息1 ssize_t send(in
t sockfd, const void *buf, size_t len, int flags);2 soc
kfd:套接字3 buf:需要发送的字符串的首元素地址4 len:需要发送的字符串的实际长度5 flags:默
认为0成功返回实际发送的字节数,失败返回-1注意:TCP并不能发送0长度报文,但是UDP可以5、recv接收消
息(默认阻塞)1 #include <sys/types.h>2 #include <sys/socket.h
>3 ssize_t recv(int sockfd, void *buf, size_t len, int
flags);4 sockfd:套接字5 buf:存放收到的消息6 len:最大能接收的长度7 flags:默
认为0成功返回收到的实际字节数, 失败返回-1recv如果收到0长度报文,表明对方已经断开连接。close(s
ockfd);断开连接6、tcp客户端 收发数据1 #include <stdio.h>2 #include
<sys/socket.h>3 #include <unistd.h>4 #include <sys/type
s.h>5 #include <netinet/in.h>6 #include <string.h>7 #in
clude <arpa/inet.h>8 int main(int argc, char const *arg
v[])9 {10 //创建TCP套接字11 int sockfd = socket(AF_INET, SOC
K_STREAM, 0);12 if (sockfd < 0)13 {14 perror("socket");
15 return 0;16 }17 printf("sockfd = %d\n", sockfd);1819
//连接服务器20 struct sockaddr_in ser_addr;21 bzero(&ser_ad
dr, sizeof(ser_addr));22 ser_addr.sin_family = AF_INET;
23 ser_addr.sin_port = htons(8000);24 ser_addr.sin_addr
.s_addr = inet_addr("10.9.11.251");25 connect(sockfd, (
struct sockaddr *)&ser_addr, sizeof(ser_addr));2627 //s
end发送数据28 send(sockfd, "hello tcp", strlen("hello tcp")
, 0);2930 //recv接收数据31 unsigned char buf[1500] = "";32
int len = recv(sockfd, buf, sizeof(buf), 0);33 printf("
len=%d buf=%s\n", len, buf);3435 //关闭套接字36 close(sockfd
);3738 return 0;39 }知识点2【TCP服务器编程】1、作为服务器的条件需要bind函数 为服
务器绑定固定的端口、IP使用listen函数 让服务器套接字 由主动变被动。等待客户端的连接到来 accept
提取到来的客户端。2、listen 监听函数1 #include <sys/types.h> /* See N
OTES */2 #include <sys/socket.h>34 int listen(int sockf
d, int backlog);功能:1、将sockfd由主动变被动,并且对sockfd进行监听客户端的连接到
来2、backlog代表的是连接队列的大小(客户端的个数)3、accept提取客户端的连接(阻塞)accept
只能从连接队列中的完成部分 提取连接。1 #include <sys/types.h> /* See NOTE
S */2 #include <sys/socket.h>3 int accept(int sockfd, s
truct sockaddr *addr, socklen_t *addrlen);sockfd:监听套接字a
ddr:存放的是客户端的地址信息addrlen:地址结构长度返回值:成功返回一个已连接套接字 代表服务器和该客
户端的连接端点(真正和客户端的连接)失败:返回值-1注意:调用一次 只能提取一个客户端对应的连接,如果连接队列
没有完成的连接 将阻塞。1 #include <stdio.h>2 #include <sys/socket.
h>3 #include <unistd.h>4 #include <sys/types.h>5 #inclu
de <netinet/in.h>6 #include <string.h>7 #include <arpa/
inet.h>8 int main(int argc, char const *argv[])9 {10 //
创建tcp套接字(如果是服务器 该套接字为监听套接字)11 int lfd = socket(AF_INET,
SOCK_STREAM, 0);1213 //bind绑定固定的IP、PORT14 struct socka
ddr_in my_addr;15 bzero(&my_addr, sizeof(my_addr));16 m
y_addr.sin_family = AF_INET;17 my_addr.sin_port = htons
(9000);18 my_addr.sin_addr.s_addr = htonl(INADDR_ANY);1
920 int ret = bind(lfd, (struct sockaddr *)&my_addr, si
zeof(my_addr));21 if (ret < 0)22 {23 perror("bind");24
return 0;25 }2627 //使用listen 对lfd进行监听客户端连接到来28 listen(l
fd, 10);2930 //提取客户端的连接31 struct sockaddr_in client_add
r;32 socklen_t client_len = sizeof(client_addr);33 int
cfd = accept(lfd, (struct sockaddr *)&client_addr, &cli
ent_len);3435 char ip_str[16] = "";36 unsigned short po
rt = 0;37 inet_ntop(AF_INET, &client_addr.sin_addr.s_ad
dr, ip_str, 16);38 port = ntohs(client_addr.sin_port);3
9 printf("连接:%s %hu到来了\n", ip_str, port);4041 //从已连接套接字
cfd 获取客户端的请求42 unsigned char buf[256] = "";43 recv(cfd,
buf, sizeof(buf), 0);44 printf("客户端的请求:%s\n", buf);454
6 //服务器应答客户端47 send(cfd, "ok", 2, 0);4849 close(lfd);50
close(cfd);51 return 0;52 }知识点3【close关闭套接字】1、作为客户端clos
e(套接字):断开当前的连接,导致服务器收到0长度 报文2、作为服务器close(监听套接字):该服务器 不能
监听新的连接到来 但是不影响 已有连接close(已连接套接字):只是断开与当前客户端的连接,不会影响监听套接
字(服务器可以继续监听新的连接到来)知识点4【三次握手】(重要)当客户端调用connect连接服务器时,底层会
发起3次握手信号,当3次握手信号完成,connect才会解阻塞往下执行。SYN:置1 表示该报文是连接请求报文
FIN:置1 表示该报文是关闭连接请求报文ACK:置1 表示当前为回应报文紧急URG: 此位置1,表明紧急指针
字段有效,它告诉系统此报文段中有紧急数据,应尽快传送PSH:推送报文RST:复位连接注意:序号:当前报文的编号
确认序号:当前报文希望接下来 对方发送的报文编号知识点5【四次挥手】(重要)45是客户端,251是服务器知识点
6【并发服务器--多进程版】并发服务器:服务器可以同时服务多个客户端案例1:多进程版本---echo并发服务器
客户端 发啥 服务器 回啥1 #include <stdio.h>2 #include <stdlib.h>3
#include <errno.h> //errno全局变量4 #include <sys/socket.h
>5 #include <unistd.h>6 #include <sys/types.h>7 #includ
e <netinet/in.h>8 #include <string.h>9 #include <arpa/i
net.h>10 #include <signal.h>11 #include <sys/wait.h>12
//信号的注册函数13 void waitpid_func(int signo)14 {15 while (1
)16 {17 pid_t pid = waitpid(‐1, NULL, WNOHANG);18 if (p
id > 0)19 {20 printf("子进程%d退出了\n", pid);21 }22 else if
(pid <= 0)23 break;24 }25 }26 void deal_client_fun(int
cfd)27 {28 while (1)29 {30 unsigned char buf[1500] = ""
;31 int len = recv(cfd, buf, sizeof(buf), 0);32 if (len
<= 0) //客户端断开连接 会导致 服务器收到0长度报文33 break;34 send(cfd, bu
f, len, 0);35 }3637 return;38 }39 int main(int argc, ch
ar const *argv[])40 {41 if (argc != 2)42 {43 printf("./
a.out 8000\n");44 return 0;45 }4647 //创建TCP服务器48 int lf
d = socket(AF_INET, SOCK_STREAM, 0);49 if (lfd < 0)50 {
51 perror("socket");52 _exit(‐1);53 }5455 //绑定固定端口56 st
ruct sockaddr_in my_addr;57 bzero(&my_addr, sizeof(my_a
ddr));58 my_addr.sin_family = AF_INET;59 my_addr.sin_po
rt = htons(atoi(argv[1]));60 my_addr.sin_addr.s_addr =
htonl(INADDR_ANY);61 int ret = bind(lfd, (struct sockad
dr *)&my_addr, sizeof(my_addr));62 if (ret < 0)63 {64 p
error("bind");65 _exit(‐1);66 }6768 //监听套接字69 listen(lf
d, 10);7071 struct sockaddr_in client_addr;72 socklen_t
client_len = sizeof(client_addr);7374 //给SIGCHLD信号注册 处
理函数75 signal(SIGCHLD, waitpid_func);//防止子进程出现僵尸进程7677 /
/循环提取客户端78 while (1)79 {80 int cfd = accept(lfd, (struc
t sockaddr *)&client_addr, &client_len);81 if (cfd < 0)
82 {83 if ((errno == ECONNABORTED) || (errno == EINTR))
84 continue;85 perror("accept");86 }87 else //提取到正常的客户端
88 {89 char ip_str[16] = "";90 unsigned short port = 0;
91 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_
str, 16);92 port = ntohs(client_addr.sin_port);93 print
f("连接:%s %hu到来了\n", ip_str, port);9495 pid_t pid = fork
();96 if (pid == 0) //子进程 服务客户端97 {98 //子进程中lfd无意义99 cl
ose(lfd);100101 //子进程 对客户端的 应答程序102 deal_client_fun(cfd
);103104 //关闭 已连接套接诶自己cfd105 close(cfd);106107 //退出108
_exit(‐1);109 }110 else //父进程 继续建立提取新的客户端111 {112 //已连接
套接字 无意义113 close(cfd);114 }115 }116 }117118 //关闭监听套接字11
9 close(lfd);120121 return 0;122 }知识点7【端口复用概述】(了解)默认的情况
下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个
端口( 8000 )端口复用:允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错SO_REUSEAD
DR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)1、当有一个有相同本地地址和端口的
socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到
该选项。2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的I
P地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。3、SO_REUSEADDR允
许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UN
Pv1。4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP注
意:置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用知识点8【设置套接字端口复
用】(了解)1 int opt = 1;2 setsockopt(sockfd, SOL_SOCKET, SO
_REUSEADDR, &opt, sizeof(opt));上面的sockfd为需要使用同一端口复用的的套接
字知识点9【多线程并发服务器】多线程版本---echo并发服务器1 #include <stdio.h>2 #
include <stdlib.h>3 #include <errno.h> //errno全局变量4 #in
clude <sys/socket.h>5 #include <unistd.h>6 #include <sy
s/types.h>7 #include <netinet/in.h>8 #include <string.h
>9 #include <arpa/inet.h>10 #include <signal.h>11 #incl
ude <pthread.h>12 typedef struct13 {14 int cfd;15 char
ip_str[16];16 unsigned short port;17 } CLI_MSG;18 int l
fd = 0;19 //信号的注册函数20 void exit_func(int signo)21 {22 c
lose(lfd);23 _exit(‐1);24 }25 void *deal_client_fun(voi
d *arg)26 {27 CLI_MSG tmp = *(CLI_MSG *)arg;28 printf("
连接:%s %hu到来了\n", tmp.ip_str, tmp.port);2930 while (1)31
{32 unsigned char buf[1500] = "";33 int len = recv(tmp
.cfd, buf, sizeof(buf), 0);34 if (len <= 0) //客户端断开连接 会
导致 服务器收到0长度报文35 {36 printf("连接:%s %hu退出了\n", tmp.ip_str
, tmp.port);37 close(tmp.cfd);38 break; //一定要记得<=039 }4
041 send(tmp.cfd, buf, len, 0);42 }4344 pthread_exit(NU
LL);45 return NULL;46 }47 int main(int argc, char const
*argv[])48 {49 if (argc != 2)50 {51 printf("./a.out 80
00\n");52 return 0;53 }5455 //创建TCP服务器56 lfd = socket(A
F_INET, SOCK_STREAM, 0);57 if (lfd < 0)58 {59 perror("s
ocket");60 _exit(‐1);61 }6263 //端口复用64 int opt = 1;65 s
etsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(o
pt));6667 //绑定固定端口68 struct sockaddr_in my_addr;69 bzer
o(&my_addr, sizeof(my_addr));70 my_addr.sin_family = AF
_INET;71 my_addr.sin_port = htons(atoi(argv[1]));72 my_
addr.sin_addr.s_addr = htonl(INADDR_ANY);73 int ret = b
ind(lfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
74 if (ret < 0)75 {76 perror("bind");77 _exit(‐1);78 }7
980 //监听套接字81 listen(lfd, 10);8283 struct sockaddr_in c
lient_addr;84 socklen_t client_len = sizeof(client_addr
);8586 //给SIGINT信号注册 处理函数87 signal(SIGINT, exit_func);
//结束服务器时关闭监听套接字8889 //循环提取客户端90 while (1)91 {92 int cfd
= accept(lfd, (struct sockaddr *)&client_addr, &client
_len);93 if (cfd < 0)94 {95 if ((errno == ECONNABORTED)
|| (errno == EINTR))96 continue;97 perror("accept");98
}99 else //提取到正常的客户端100 {101 CLI_MSG tmp;102 tmp.cfd =
cfd;103 inet_ntop(AF_INET, &client_addr.sin_addr.s_add
r, tmp.ip_str,6);104 tmp.port = ntohs(client_addr.sin_p
ort);105106 //创建线程服务器客户端107 pthread_t tid;108 pthread_c
reate(&tid, NULL, deal_client_fun, (void *)&tmp);109 pt
hread_detach(tid);110 }111 }112113 //关闭监听套接字114 close(l
fd);115116 return 0;117 }知识点10【web服务器】html(超文本标记语言)显示文本
http(超文本传送协议) 传输协议URL 统一地址定位符web服务器使用的传送协议:http(超文本传送协议
) 基于TCP客户端是浏览器 服务器(用户实现)浏览器只需要提供请求的方式(GET\POST)每一个客户端 只
能有一个请求浏览器的请求方式:解析文件名,打开本地文件(成功、失败)服务器应答的格式:请求失败HTTP传送文件
没有固定大小限制。1 #include <stdio.h>2 #include <stdlib.h>3 #in
clude <errno.h> //errno全局变量4 #include <sys/socket.h>5 #
include <unistd.h>6 #include <sys/types.h>7 #include <n
etinet/in.h>8 #include <string.h>9 #include <arpa/inet.
h>10 #include <signal.h>11 #include <pthread.h>12 #incl
ude <sys/stat.h>13 #include <fcntl.h>14 char err[] = "H
TTP/1.1 404 Not Found\r\n"15 "Content‐Type: text/html\r
\n"16 "\r\n"17 "<HTML><BODY>File not found</BODY></HTML
>";18 char head[] = "HTTP/1.1 200 OK\r\n"19 "Content‐Ty
pe: text/html\r\n"20 "\r\n";21 typedef struct22 {23 int
cfd;24 char ip_str[16];25 unsigned short port;26 } CLI
_MSG;27 int lfd = 0;28 //信号的注册函数29 void exit_func(int s
igno)30 {31 close(lfd);32 _exit(‐1);33 }3435 //客户端核心任务线
程函数36 void *deal_client_fun(void *arg)37 {38 CLI_MSG tm
p = *(CLI_MSG *)arg;39 printf("连接:%s %hu到来了\n", tmp.ip_
str, tmp.port);4041 //获取浏览器的请求42 unsigned char cmd_buf[
1024] = "";43 int len = recv(tmp.cfd, cmd_buf, sizeof(c
md_buf), 0);44 if (len <= 0)45 {46 close(tmp.cfd);47 pt
hread_exit(NULL);48 }4950 //解析浏览器的请求51 char file_name[1
28] = "./html/";52 sscanf(cmd_buf, "GET /%[^ ]", file_n
ame + 7);53 if (file_name[7] == '\0')54 {55 strcat(file
_name, "index.html");56 }57 printf("file_name=##%s##\n"
, file_name);5859 //open打开本地文件60 int fd = open(file_nam
e, O_RDONLY);61 if (fd < 0)62 {63 //告诉浏览器40464 send(tmp
.cfd, err, strlen(err), 0);6566 close(tmp.cfd);67 perro
r("open");68 pthread_exit(NULL);69 }7071 //告诉浏览器 200 打开
成功准备接受72 send(tmp.cfd, head, strlen(head), 0);7374 //循环
读取本地文件数据发送给浏览器75 while (1)76 {77 unsigned char buf[512]
= "";78 //读取本地文件数据79 int len = read(fd, buf, sizeof(bu
f));80 send(tmp.cfd, buf, len, 0);81 if (len < 512)82 b
reak;83 }8485 close(fd);86 close(tmp.cfd);8788 pthread_
exit(NULL);89 return NULL;90 }91 int main(int argc, cha
r const *argv[])92 {93 if (argc != 2)94 {95 printf("./a
.out 8000\n");96 return 0;97 }9899 //创建TCP服务器100 lfd =
socket(AF_INET, SOCK_STREAM, 0);101 if (lfd < 0)102 {10
3 perror("socket");104 _exit(‐1);105 }106107 //端口复用108
int opt = 1;109 setsockopt(lfd, SOL_SOCKET, SO_REUSEADD
R, &opt, sizeof(opt));110111 //绑定固定端口112 struct sockadd
r_in my_addr;113 bzero(&my_addr, sizeof(my_addr));114 m
y_addr.sin_family = AF_INET;115 my_addr.sin_port = hton
s(atoi(argv[1]));116 my_addr.sin_addr.s_addr = htonl(IN
ADDR_ANY);117 int ret = bind(lfd, (struct sockaddr *)&m
y_addr, sizeof(my_addr));118 if (ret < 0)119 {120 perro
r("bind");121 _exit(‐1);122 }123124 //监听套接字125 listen(l
fd, 10);126127 struct sockaddr_in client_addr;128 sockl
en_t client_len = sizeof(client_addr);129130 //给SIGINT信
号注册 处理函数131 signal(SIGINT, exit_func); //结束服务器时关闭监听套接字1
32133 //循环提取客户端134 while (1)135 {136 int cfd = accept(l
fd, (struct sockaddr *)&client_addr, &client_len);137 i
f (cfd < 0)138 {139 if ((errno == ECONNABORTED) || (err
no == EINTR))140 continue;141 perror("accept");142 }143
else //提取到正常的客户端144 {145 CLI_MSG tmp;146 tmp.cfd = cfd
;147 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, t
mp.ip_str,6);148 tmp.port = ntohs(client_addr.sin_port)
;149150 //创建线程服务器客户端151 pthread_t tid;152 pthread_creat
e(&tid, NULL, deal_client_fun, (void *)&tmp);153 pthrea
d_detach(tid);154 }155 }156157 //关闭监听套接字158 close(lfd);
159160 return 0