synchronous同步的
TCP(Transmission Control Protocol) 传输控制协议
位码即tcp标志位,有6种标示:
SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)
TCP(Transmission Control Protocol) 传输控制协议
位码即tcp标志位,有6种标示:
SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)
Sequence number(顺序号码) Acknowledge number(确认号码)
************************************************************************************************************8
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
************************************************************************************************************8
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,
主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
完成三次握手,主机A与主机B开始传送数据。
************************************************************************************************************
四次分手:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止
这个方向的
连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。
************************************************************************************************************
四次分手:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止
这个方向的
连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。
************************************************************************************************************
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。
而这两种状态的区别 是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,
此时该SOCKET即
进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,
都应该马 上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
************************************************************************************************************
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态? 报文最长生命 maximum segment lifetime
************************************************************************************************************
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。
而这两种状态的区别 是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,
此时该SOCKET即
进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,
都应该马 上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
************************************************************************************************************
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态? 报文最长生命 maximum segment lifetime
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND
状态到
ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,
因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重
发可能丢失
的ACK报文。
************************************************************************************************************
int socket(int domain, int type, int pritocol);
状态到
ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,
因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重
发可能丢失
的ACK报文。
************************************************************************************************************
int socket(int domain, int type, int pritocol);
domain参数标识系统用的那个底层协议族,我们示例程序中用的是AF_INET,表示用于ipv4 。
type 指定服务类型,SOCK_STREAM是流式服务,SOCK_UGRAM是数据报服务(用于UDP通信中)。
protocol指在前两个参数确定的情况的 下,再选择一个具体的协议。不过一般这个值把它设置为0,表示默认协议。
函数返回整形的socket文件描述符,失败返回-1,并设置errno.
************************************************************************************************************
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
************************************************************************************************************
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
bind将my_addr所指的socket地址分配给未命名sockfd文件描述符,addrlen参数支出该socket地址的长度。
bind
成功时返回0,失败时返回-1并设置errron,常见errno是EACCES(被绑定的地址受保护),EADDRINUSE(被绑定的地址使用中
)。
************************************************************************************************************
监听:socket被命名后不能马上接受客户连接,需要使用系统调用来建一个监听队列以存放待处理的客户连接。
成功时返回0,失败时返回-1并设置errron,常见errno是EACCES(被绑定的地址受保护),EADDRINUSE(被绑定的地址使用中
)。
************************************************************************************************************
监听:socket被命名后不能马上接受客户连接,需要使用系统调用来建一个监听队列以存放待处理的客户连接。
int listen(int sockfd, int backlog);
第一个参数指定监听的socke,backlog提示内核监听队列的最大长度。
监听队列的长度如果唱过backlog,服务器将不受理新的客户连接,客户端也将收到 CONNECTREFUSED信息。
listen成功返回0,失败返回-1并设置errno。
************************************************************************************************************
int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
accept成功时返回一个新的连接socket,该socket地址唯一的标识了被接受的这个连接,服务器可以通过读写该socket来与被接
受连接对应
的客户端通信。accept失败时返回-1并设置errno。
自此,服务器端在while(1)的控制下可以一直处于等待连接状态。
************************************************************************************************************
ssize_t recv((int sockfd, void *buf, size_t len, int flags);
监听队列的长度如果唱过backlog,服务器将不受理新的客户连接,客户端也将收到 CONNECTREFUSED信息。
listen成功返回0,失败返回-1并设置errno。
************************************************************************************************************
int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
accept成功时返回一个新的连接socket,该socket地址唯一的标识了被接受的这个连接,服务器可以通过读写该socket来与被接
受连接对应
的客户端通信。accept失败时返回-1并设置errno。
自此,服务器端在while(1)的控制下可以一直处于等待连接状态。
************************************************************************************************************
ssize_t recv((int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
recv读取sockfd上的数据,buf、len参数分别指定读缓冲区的位置和大小,flags为数据收发提供了额外的控制,通常
设为0即可。
recv成功返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此可能需要多次调用recv,才能读取到完
整数据。
recv可能返回0,意味着通信对方已经关闭连接了。recv出错返回-1并设置errno。
recv读取sockfd上的数据,buf、len参数分别指定读缓冲区的位置和大小,flags为数据收发提供了额外的控制,通常
设为0即可。
recv成功返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此可能需要多次调用recv,才能读取到完
整数据。
recv可能返回0,意味着通信对方已经关闭连接了。recv出错返回-1并设置errno。
send 往sockfd上写入数据,buf和len参数分贝制定写缓冲区的位置和大小。send成功时返回实际写入的数据长度,
失败返回-1并设置errno。
************************************************************************************************************
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
serv_addr参数是服务器监听的socket地址,addrlen参数指定这个地址的长度。connect成功时返回0,一旦成功建立连接,
sockfd就唯一的标识这个连接,客户端可以通过读写sockfd来与服务器通信。connect失败返回-1,并设置errno。
失败返回-1并设置errno。
************************************************************************************************************
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
serv_addr参数是服务器监听的socket地址,addrlen参数指定这个地址的长度。connect成功时返回0,一旦成功建立连接,
sockfd就唯一的标识这个连接,客户端可以通过读写sockfd来与服务器通信。connect失败返回-1,并设置errno。
************************************************************************************************************
int close(int fd);
int close(int fd);
关闭一个连接实际上是关闭该连接对应的socket,可通过如下关闭普通文件描述符的系统调用来完成。
但是close并非立即关闭一个连接,而是将fd的引用计数减1,只有当引用计数fd为0时,才真正的关闭连接。
若在多进程中,一次fork系统调用默认将父进程中打开的socket引用计数加1,因此我们在父进程和子进程都需要调用close
才能将连接关闭。
************************************************************************************************************
如果程序是计算密集型的,并发编程并没有优势,反而由于任务的切换使效率降低。但如果程序是I/O密集型的,比如经常读写文件
,
访问数据库等,情况就不同了。由于I/O操作的速度远没有CPU的计算速度快,所以让程序阻塞于I/O于I/O操作将浪费大量的CPU时间
。
如果程序有多个执行线程,则当前被I/O操作所阻塞的执行线程可主动放弃CPU(或由操作系统来调度),并将执行权转移到其他线
程。
因此CPU就可以用来做更加有意义的事情,而不是等待I/O操作完成,因此CPU利用率显著提升。
************************************************************************************************************
int pipe(int
fd[2]);pipe函数参数是一个包含两个int型的数组指针,函数成功返回0,并将一对打开的文件描述符填入其参数指向的数组。
如果失败,则返回-1,并设置errno。
默认情况下,这一对文件描述符都是阻塞的。如果用read系统调用来读取一个空的管道,read将被阻塞,直到管道内有数据可读;
如果我们用write系统调用往一个满的管道中写入数据,则write将被阻塞,直到管道有足够多的空间可用。
但是close并非立即关闭一个连接,而是将fd的引用计数减1,只有当引用计数fd为0时,才真正的关闭连接。
若在多进程中,一次fork系统调用默认将父进程中打开的socket引用计数加1,因此我们在父进程和子进程都需要调用close
才能将连接关闭。
************************************************************************************************************
如果程序是计算密集型的,并发编程并没有优势,反而由于任务的切换使效率降低。但如果程序是I/O密集型的,比如经常读写文件
,
访问数据库等,情况就不同了。由于I/O操作的速度远没有CPU的计算速度快,所以让程序阻塞于I/O于I/O操作将浪费大量的CPU时间
。
如果程序有多个执行线程,则当前被I/O操作所阻塞的执行线程可主动放弃CPU(或由操作系统来调度),并将执行权转移到其他线
程。
因此CPU就可以用来做更加有意义的事情,而不是等待I/O操作完成,因此CPU利用率显著提升。
************************************************************************************************************
int pipe(int
fd[2]);pipe函数参数是一个包含两个int型的数组指针,函数成功返回0,并将一对打开的文件描述符填入其参数指向的数组。
如果失败,则返回-1,并设置errno。
默认情况下,这一对文件描述符都是阻塞的。如果用read系统调用来读取一个空的管道,read将被阻塞,直到管道内有数据可读;
如果我们用write系统调用往一个满的管道中写入数据,则write将被阻塞,直到管道有足够多的空间可用。
如果管道的写端文件描述符fd[1]的引用计数减少至0,即没有任何的进程要往管道写入数据,则针对该管道的读端文件描述符fd[0
]的read操作
将会返回0,意味着读到了文件结束标记(EOF);反之,如果管道的读端文件描述符fd[0]的引用计数减少至0,
即没有任何的进程需要从管道中读取数据,则针对该管道的写端文件描述符fd[1]的write操作将失败,并引发SIGPIPE信号。
管道内部传输的数据是字节流,与TCP字节流概念相同,又有细微的区别。
而管道本身拥有一个容量限制,它规定如果应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据。
自Linux2.6.11内核起,管道容量的大小默认65536字节。当然我们也可以使用fcntl函数修改管道容量。
************************************************************************************************************ς
1.C/S模型
TCP/IP协议在设计和实现上并没有客户端和服务器的概念。在通信过程中所有机器都是对等的。
但由于资源(视频、资源、软件等)都被数据提供者所垄断,所以几乎所有的网络应用程序都很自然地采用
C/S(客户端/服务器)模型:所有客户端都采用通过访问服务器来获取所需的资源。
而管道本身拥有一个容量限制,它规定如果应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据。
自Linux2.6.11内核起,管道容量的大小默认65536字节。当然我们也可以使用fcntl函数修改管道容量。
************************************************************************************************************ς
1.C/S模型
TCP/IP协议在设计和实现上并没有客户端和服务器的概念。在通信过程中所有机器都是对等的。
但由于资源(视频、资源、软件等)都被数据提供者所垄断,所以几乎所有的网络应用程序都很自然地采用
C/S(客户端/服务器)模型:所有客户端都采用通过访问服务器来获取所需的资源。
由于客户连接请求是随机到达的异步事件,服务器就要适用某种I/O模型来监听这一事件。服务器使用的是I/O复用技术之一的selec
t系统调用。
当监听到连接器请求后,服务器就调用accept函数接受它,并分配一个逻辑单元为新的连接服务。
逻辑单元可以是新创建的子进程、子线程或者其他。
************************************************************************************************************
2. P2P模型
P2P(Peer to Peer, 点对点)模型比C/S模型更符合网络通信的实际情况。
它摒弃了以服务器为中心的格局,让网络上所有主机重新回归对等的地位。
P2P模型使得每台机器在消耗服务的同时也给别人提供服务,这样资源能够充分、自由地共享。
************************************************************************************************************
众所周知,IO是计算机上最慢的部分,先不看磁盘IO,针对网络编程,自然是针对网络IO。
************************************************************************************************************
not broadcast and not multicastNBNS
hierarchy层级
************************************************************************************************************
SIGTSTP中TSTP的意思是tty stop,即在control
terminal上输入了susp即,输入了ctl-z的suspend(悬停)键。那么SIGTSTP被发送给进程。
将进程暂停是SIGTSTP的默认action,用户可以自定义一其handler,而将进程暂停是SIGSTOP的定死的action,用户不能修改。
此外,二者没什么差别,都使用SIGCONT来讲进程重新激活。
************************************************************************************************************
服务器与客户端一对一
#include <stdio.h>
2.#include <stdlib.h>
3.#include <strings.h>
4.#include <sys/types.h>
5.#include <sys/socket.h>
6.#include <memory.h>
7.#include <unistd.h>
8.//#include <linux/in.h>
9.#include <netinet/in.h>
10.//#include <linux/inet_diag.h>
11.#include <arpa/inet.h>
12.
13.#include <signal.h>
14.
15./**
16. 关于 sockaddr sockaddr_in socketaddr_un说明
17. http://maomaozaoyue.blog.sohu.com/197538359.html
18. */
19.
20.#define PORT 11910 //定义通信端口
21.#define BACKLOG 5 //定义侦听队列长度
22.#define buflen 1024
23.
24.void process_conn_server(int s);
25.void sig_pipe(int signo);
26.
27.int ss,sc; //ss为服务器socket描述符,sc为某一客户端通信socket描述符
28.
29.int main(int argc,char *argv[])
30.{
31.
32. struct sockaddr_in server_addr; //存储服务器端socket地址结构
33. struct sockaddr_in client_addr; //存储客户端 socket地址结构
34.
35. int err; //返回值
36. pid_t pid; //分叉进行的ID
37.
38. /*****************socket()***************/
39. ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流
40. if(ss<0)
41. {
42. printf("server : server socket create error\n");
43. return -1;
44. }
45. //注册信号
46. sighandler_t ret;
47. ret = signal(SIGTSTP,sig_pipe);
48. if(SIG_ERR == ret)
49. {
50. printf("信号挂接失败\n");
51. return -1;
52. }
53. else
54. printf("信号挂接成功\n");
55.
56.
57. /******************bind()****************/
58. //初始化地址结构
59. memset(&server_addr,0,sizeof(server_addr));
60. server_addr.sin_family = AF_INET; //协议族
61. server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
62. server_addr.sin_port = htons(PORT);
63.
64. err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));
65. if(err<0)
66. {
67. printf("server : bind error\n");
68. return -1;
69. }
70.
71. /*****************listen()***************/
72. err = listen(ss,BACKLOG); //设置监听的队列大小
73. if(err < 0)
74. {
75. printf("server : listen error\n");
76. return -1;
77. }
78.
79. /****************accept()***************/
80. /**
81. 为类方便处理,我们使用两个进程分别管理两个处理:
82. 1,服务器监听新的连接请求;2,以建立连接的C/S实现通信
83. 这两个任务分别放在两个进程中处理,为了防止失误操作
84. 在一个进程中关闭 侦听套接字描述符 另一进程中关闭
85. 客户端连接套接字描述符。注只有当所有套接字全都关闭时
86. 当前连接才能关闭,fork调用的时候父进程与子进程有相同的
87. 套接字,总共两套,两套都关闭掉才能关闭这个套接字
88. */
89.
90. for(;;)
91. {
92. socklen_t addrlen = sizeof(client_addr);
93. //accept返回客户端套接字描述符
94. sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此处为了获取返回值使用 指针做参数
95. if(sc < 0) //出错
96. {
97. continue; //结束此次循环
98. }
99. else
100. {
101. printf("server : connected\n");
102. }
103.
104. //创建一个子线程,用于与客户端通信
105. pid = fork();
106. //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID
107. if(pid == 0) //子进程,与客户端通信
108. {
109. close(ss);
110. process_conn_server(sc);
111. }
112. else
113. {
114. close(sc);
115. }
116. }
117.}
118.
119./**
120. 服务器对客户端连接处理过程;先读取从客户端发送来的数据,
121. 然后将接收到的数据的字节的个数发送到客户端
122. */
123.
124.//通过套接字 s 与客户端进行通信
125.void process_conn_server(int s)
126.{
127. ssize_t size = 0;
128. char buffer[buflen]; //定义数据缓冲区
129. for(;;)
130. {
131. //等待读
132. for(size = 0;size == 0 ;size = read(s,buffer,buflen));
133. //输出从客户端接收到的数据
134. printf("%s",buffer);
135.
136. //结束处理
137. if(strcmp(buffer,"quit") == 0)
138. {
139. close(s); //成功返回0,失败返回-1
140. return ;
141. }
142. sprintf(buffer,"%d bytes altogether\n",size);
143. write(s,buffer,strlen(buffer)+1);
144. }
145.}
146.void sig_pipe(int signo)
147.{
148. printf("catch a signal\n");
149. if(signo == SIGTSTP)
150. {
151. printf("接收到 SIGTSTP 信号\n");
152. int ret1 = close(ss);
153. int ret2 = close(sc);
154. int ret = ret1>ret2?ret1:ret2;
155. if(ret == 0)
156. printf("成功 : 关闭套接字\n");
157. else if(ret ==-1 )
158. printf("失败 : 未关闭套接字\n");
159.
160. exit(1);
161. }
162.}
t系统调用。
当监听到连接器请求后,服务器就调用accept函数接受它,并分配一个逻辑单元为新的连接服务。
逻辑单元可以是新创建的子进程、子线程或者其他。
************************************************************************************************************
2. P2P模型
P2P(Peer to Peer, 点对点)模型比C/S模型更符合网络通信的实际情况。
它摒弃了以服务器为中心的格局,让网络上所有主机重新回归对等的地位。
P2P模型使得每台机器在消耗服务的同时也给别人提供服务,这样资源能够充分、自由地共享。
************************************************************************************************************
众所周知,IO是计算机上最慢的部分,先不看磁盘IO,针对网络编程,自然是针对网络IO。
************************************************************************************************************
not broadcast and not multicastNBNS
hierarchy层级
************************************************************************************************************
SIGTSTP中TSTP的意思是tty stop,即在control
terminal上输入了susp即,输入了ctl-z的suspend(悬停)键。那么SIGTSTP被发送给进程。
将进程暂停是SIGTSTP的默认action,用户可以自定义一其handler,而将进程暂停是SIGSTOP的定死的action,用户不能修改。
此外,二者没什么差别,都使用SIGCONT来讲进程重新激活。
************************************************************************************************************
服务器与客户端一对一
#include <stdio.h>
2.#include <stdlib.h>
3.#include <strings.h>
4.#include <sys/types.h>
5.#include <sys/socket.h>
6.#include <memory.h>
7.#include <unistd.h>
8.//#include <linux/in.h>
9.#include <netinet/in.h>
10.//#include <linux/inet_diag.h>
11.#include <arpa/inet.h>
12.
13.#include <signal.h>
14.
15./**
16. 关于 sockaddr sockaddr_in socketaddr_un说明
17. http://maomaozaoyue.blog.sohu.com/197538359.html
18. */
19.
20.#define PORT 11910 //定义通信端口
21.#define BACKLOG 5 //定义侦听队列长度
22.#define buflen 1024
23.
24.void process_conn_server(int s);
25.void sig_pipe(int signo);
26.
27.int ss,sc; //ss为服务器socket描述符,sc为某一客户端通信socket描述符
28.
29.int main(int argc,char *argv[])
30.{
31.
32. struct sockaddr_in server_addr; //存储服务器端socket地址结构
33. struct sockaddr_in client_addr; //存储客户端 socket地址结构
34.
35. int err; //返回值
36. pid_t pid; //分叉进行的ID
37.
38. /*****************socket()***************/
39. ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流
40. if(ss<0)
41. {
42. printf("server : server socket create error\n");
43. return -1;
44. }
45. //注册信号
46. sighandler_t ret;
47. ret = signal(SIGTSTP,sig_pipe);
48. if(SIG_ERR == ret)
49. {
50. printf("信号挂接失败\n");
51. return -1;
52. }
53. else
54. printf("信号挂接成功\n");
55.
56.
57. /******************bind()****************/
58. //初始化地址结构
59. memset(&server_addr,0,sizeof(server_addr));
60. server_addr.sin_family = AF_INET; //协议族
61. server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
62. server_addr.sin_port = htons(PORT);
63.
64. err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));
65. if(err<0)
66. {
67. printf("server : bind error\n");
68. return -1;
69. }
70.
71. /*****************listen()***************/
72. err = listen(ss,BACKLOG); //设置监听的队列大小
73. if(err < 0)
74. {
75. printf("server : listen error\n");
76. return -1;
77. }
78.
79. /****************accept()***************/
80. /**
81. 为类方便处理,我们使用两个进程分别管理两个处理:
82. 1,服务器监听新的连接请求;2,以建立连接的C/S实现通信
83. 这两个任务分别放在两个进程中处理,为了防止失误操作
84. 在一个进程中关闭 侦听套接字描述符 另一进程中关闭
85. 客户端连接套接字描述符。注只有当所有套接字全都关闭时
86. 当前连接才能关闭,fork调用的时候父进程与子进程有相同的
87. 套接字,总共两套,两套都关闭掉才能关闭这个套接字
88. */
89.
90. for(;;)
91. {
92. socklen_t addrlen = sizeof(client_addr);
93. //accept返回客户端套接字描述符
94. sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此处为了获取返回值使用 指针做参数
95. if(sc < 0) //出错
96. {
97. continue; //结束此次循环
98. }
99. else
100. {
101. printf("server : connected\n");
102. }
103.
104. //创建一个子线程,用于与客户端通信
105. pid = fork();
106. //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID
107. if(pid == 0) //子进程,与客户端通信
108. {
109. close(ss);
110. process_conn_server(sc);
111. }
112. else
113. {
114. close(sc);
115. }
116. }
117.}
118.
119./**
120. 服务器对客户端连接处理过程;先读取从客户端发送来的数据,
121. 然后将接收到的数据的字节的个数发送到客户端
122. */
123.
124.//通过套接字 s 与客户端进行通信
125.void process_conn_server(int s)
126.{
127. ssize_t size = 0;
128. char buffer[buflen]; //定义数据缓冲区
129. for(;;)
130. {
131. //等待读
132. for(size = 0;size == 0 ;size = read(s,buffer,buflen));
133. //输出从客户端接收到的数据
134. printf("%s",buffer);
135.
136. //结束处理
137. if(strcmp(buffer,"quit") == 0)
138. {
139. close(s); //成功返回0,失败返回-1
140. return ;
141. }
142. sprintf(buffer,"%d bytes altogether\n",size);
143. write(s,buffer,strlen(buffer)+1);
144. }
145.}
146.void sig_pipe(int signo)
147.{
148. printf("catch a signal\n");
149. if(signo == SIGTSTP)
150. {
151. printf("接收到 SIGTSTP 信号\n");
152. int ret1 = close(ss);
153. int ret2 = close(sc);
154. int ret = ret1>ret2?ret1:ret2;
155. if(ret == 0)
156. printf("成功 : 关闭套接字\n");
157. else if(ret ==-1 )
158. printf("失败 : 未关闭套接字\n");
159.
160. exit(1);
161. }
162.}
客户端代码:
[cpp] view plain copy
1. #include <stdio.h>
2. #include <strings.h>
3. #include <unistd.h>
4. #include <sys/types.h>
5. #include <sys/socket.h>
6. //#include <linux/in.h>
7. #include <stdlib.h>
8. #include <memory.h>
9. #include <arpa/inet.h>
10. #include <netinet/in.h>
11.
12. #include <signal.h> //添加信号处理 防止向已断开的连接通信
13.
14. /**
15. 信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号,
16. 信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认
17. 响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行
18. */
19.
20.
21. #define PORT 11910
22. #define Buflen 1024
23.
24. void process_conn_client(int s);
25. void sig_pipe(int signo); //用户注册的信号函数,接收的是信号值
26.
27. int s; //全局变量 , 存储套接字描述符
28.
29. int main(int argc,char *argv[])
30. {
31.
32. sockaddr_in server_addr;
33. int err;
34. sighandler_t ret;
35. char server_ip[50] = "";
36. int port = 0;
37. strcpy(server_ip, argv[1]);
38. port = atoi(argv[2]);
39.
40. /********************socket()*********************/
41. s= socket(AF_INET,SOCK_STREAM,0);
42. if(s<0)
43. {
44. printf("client : create socket error\n");
45. return 1;
46. }
47. printf("client : socket fd = %d\n", s);
48. //信号处理函数 SIGINT 是当用户按一个 Ctrl-C 建时发送的信号
49. ret = signal(SIGTSTP,sig_pipe);
50. if(SIG_ERR == ret)
51. {
52. printf("信号挂接失败\n");
53. return -1;
54. }
55. else
56. printf("信号挂接成功\n") ;
57.
58.
59. /*******************connect()*********************/
60. //设置服务器地址结构,准备连接到服务器
61. memset(&server_addr,0,sizeof(server_addr));
62. server_addr.sin_family = AF_INET;
63. server_addr.sin_port = htons(PORT);
64. server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
65.
66. /*将用户数入对额字符串类型的IP格式转化为整型数据*/
67. //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);
68. printf("please input server ip address : \n");
69. read(0,server_ip,50);
70. //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);
71. server_addr.sin_addr.s_addr = inet_addr(server_ip);
72.
73. err = connect(s,(struct sockaddr *)&server_addr,sizeof(sockaddr));
74. if(err == 0)
75. {
76. printf("client : connect to server\n");
77. }
78. else
79. {
80. printf("client : connect error\n");
81. return -1;
82. }
83. //与服务器端进行通信
84. process_conn_client(s);
85. close(s);
86.
87. }
88. void process_conn_client(int s)
89. {
90.
91. ssize_t size = 0;
92. char buffer[Buflen];
93.
94. for(;;)
95. {
96. memset(buffer,'\0',Buflen);
97. /*从标准输入中读取数据放到缓冲区buffer中*/
98. size = read(0,buffer,Buflen); // 0,被默认的分配到标准输入 1,标准输出 2,error
99. if(size > 0)
100. {
101. //当向服务器发送 “quit” 命令时,服务器首先断开连接
102. write(s,buffer,strlen(buffer)+1); //向服务器端写
103.
104. //等待读取到数据
105. for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );
106.
107. write(1,buffer,strlen(buffer)+1); //向标准输出写
108. }
109. }
110. }
111.
112. void sig_pipe(int signo) //传入套接字描述符
113. {
114. printf("Catch a signal\n");
115. if(signo == SIGTSTP)
116. {
117.
118. printf("接收到 SIGTSTP 信号\n");
119. int ret = close(s);
120. if(ret == 0)
121. printf("成功 : 关闭套接字\n");
122. else if(ret ==-1 )
123. printf("失败 : 未关闭套接字\n");
124. exit(1);
125. }
126. }
2. #include <strings.h>
3. #include <unistd.h>
4. #include <sys/types.h>
5. #include <sys/socket.h>
6. //#include <linux/in.h>
7. #include <stdlib.h>
8. #include <memory.h>
9. #include <arpa/inet.h>
10. #include <netinet/in.h>
11.
12. #include <signal.h> //添加信号处理 防止向已断开的连接通信
13.
14. /**
15. 信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号,
16. 信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认
17. 响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行
18. */
19.
20.
21. #define PORT 11910
22. #define Buflen 1024
23.
24. void process_conn_client(int s);
25. void sig_pipe(int signo); //用户注册的信号函数,接收的是信号值
26.
27. int s; //全局变量 , 存储套接字描述符
28.
29. int main(int argc,char *argv[])
30. {
31.
32. sockaddr_in server_addr;
33. int err;
34. sighandler_t ret;
35. char server_ip[50] = "";
36. int port = 0;
37. strcpy(server_ip, argv[1]);
38. port = atoi(argv[2]);
39.
40. /********************socket()*********************/
41. s= socket(AF_INET,SOCK_STREAM,0);
42. if(s<0)
43. {
44. printf("client : create socket error\n");
45. return 1;
46. }
47. printf("client : socket fd = %d\n", s);
48. //信号处理函数 SIGINT 是当用户按一个 Ctrl-C 建时发送的信号
49. ret = signal(SIGTSTP,sig_pipe);
50. if(SIG_ERR == ret)
51. {
52. printf("信号挂接失败\n");
53. return -1;
54. }
55. else
56. printf("信号挂接成功\n") ;
57.
58.
59. /*******************connect()*********************/
60. //设置服务器地址结构,准备连接到服务器
61. memset(&server_addr,0,sizeof(server_addr));
62. server_addr.sin_family = AF_INET;
63. server_addr.sin_port = htons(PORT);
64. server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
65.
66. /*将用户数入对额字符串类型的IP格式转化为整型数据*/
67. //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);
68. printf("please input server ip address : \n");
69. read(0,server_ip,50);
70. //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);
71. server_addr.sin_addr.s_addr = inet_addr(server_ip);
72.
73. err = connect(s,(struct sockaddr *)&server_addr,sizeof(sockaddr));
74. if(err == 0)
75. {
76. printf("client : connect to server\n");
77. }
78. else
79. {
80. printf("client : connect error\n");
81. return -1;
82. }
83. //与服务器端进行通信
84. process_conn_client(s);
85. close(s);
86.
87. }
88. void process_conn_client(int s)
89. {
90.
91. ssize_t size = 0;
92. char buffer[Buflen];
93.
94. for(;;)
95. {
96. memset(buffer,'\0',Buflen);
97. /*从标准输入中读取数据放到缓冲区buffer中*/
98. size = read(0,buffer,Buflen); // 0,被默认的分配到标准输入 1,标准输出 2,error
99. if(size > 0)
100. {
101. //当向服务器发送 “quit” 命令时,服务器首先断开连接
102. write(s,buffer,strlen(buffer)+1); //向服务器端写
103.
104. //等待读取到数据
105. for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );
106.
107. write(1,buffer,strlen(buffer)+1); //向标准输出写
108. }
109. }
110. }
111.
112. void sig_pipe(int signo) //传入套接字描述符
113. {
114. printf("Catch a signal\n");
115. if(signo == SIGTSTP)
116. {
117.
118. printf("接收到 SIGTSTP 信号\n");
119. int ret = close(s);
120. if(ret == 0)
121. printf("成功 : 关闭套接字\n");
122. else if(ret ==-1 )
123. printf("失败 : 未关闭套接字\n");
124. exit(1);
125. }
126. }