linux send与recv函数详解
1 #include <sys/socket.h>
2 ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
3 ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
recv 和send的前3个参数等同于read和write。
flags参数值为0或:
flags | 说明 | recv | send |
MSG_DONTROUTE | 绕过路由表查找 | | • |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • | |
MSG_WAITALL | 等待所有数据 | • | |
1. send解析
sockfd:指定发送端套接字描述符。
buff: 存放要发送数据的缓冲区
nbytes: 实际要改善的数据的字节数
flags: 一般设置为0
1) send先比较发送数据的长度nbytes和套接字sockfd的发送缓冲区的长度,如果nbytes > 套接字sockfd的发送缓冲区的长度, 该函数返回SOCKET_ERROR;
2) 如果nbtyes <= 套接字sockfd的发送缓冲区的长度,那么send先检查协议是否正在发送sockfd的发送缓冲区中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲区中的数据或者sockfd的发送缓冲区中没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和nbytes
3) 如果 nbytes > 套接字sockfd的发送缓冲区剩余空间的长度,send就一起等待协议把套接字sockfd的发送缓冲区中的数据发送完
4) 如果 nbytes < 套接字sockfd的发送缓冲区剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把套接字sockfd的发送缓冲区中的数据传到连接的另一端的,而是协议传送的,send仅仅是把buf中的数据copy到套接字sockfd的发送缓冲区的剩余空间里)。
5) 如果send函数copy成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR; 如果在等待协议传送数据时网络断开,send函数也返回SOCKET_ERROR。
6) send函数把buff中的数据成功copy到sockfd的改善缓冲区的剩余空间后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每一个除send的socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传递完毕才能继续,如果在等待时出现网络错误那么该socket函数就返回SOCKET_ERROR)
7) 在unix系统下,如果send在等待协议传送数据时网络断开,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的处理是进程终止。
2.recv函数
sockfd: 接收端套接字描述符
buff: 用来存放recv函数接收到的数据的缓冲区
nbytes: 指明buff的长度
flags: 一般置为0
1) recv先等待s的发送缓冲区的数据被协议传送完毕,如果协议在传送sock的发送缓冲区中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR
2) 如果套接字sockfd的发送缓冲区中没有数据或者数据被协议成功发送完毕后,recv先检查套接字sockfd的接收缓冲区,如果sockfd的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一起等待,直到把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲区中的数据copy到buff中(注意协议接收到的数据可能大于buff的长度,所以在这种情况下要调用几次recv函数才能把sockfd的接收缓冲区中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)
3) recv函数返回其实际copy的字节数,如果recv在copy时出错,那么它返回SOCKET_ERROR。如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
4) 在unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用 recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
1. //client.c
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <string.h>
5. #include <unistd.h>
6. #include <sys/types.h>
7. #include <sys/socket.h>
8. #include <arpa/inet.h>
9. #include <netinet/in.h>
10. #include <fcntl.h>
11.
12. #define N 256
13.
14. typedef struct sockaddr SA;
15.
16. void commd_help();
17. void commd_exit();
18. void commd_ls(struct sockaddr_in, char *);
19. void commd_get(struct sockaddr_in , char *);
20. void commd_put(struct sockaddr_in , char *);
21.
22. int main(int argc, char *argv[])
23. {
24. char commd[N];
25. struct sockaddr_in addr;
26. int len;
27. sizeof(addr)); //将&addr中的前sizeof(addr)字节置为0,包括'\0'
28. //AF_INET代表TCP/IP协议
29. "127.0.0.1"); //将点间隔地址转换为网络字节顺序
30. //转换为网络字节顺序
31. sizeof(addr);
32.
33. while(1)
34. {
35. "ftp>");
36. bzero(commd,N);
37. //fgets函数从stdin流中读取N-1个字符放入commd中
38. if(fgets(commd,N,stdin) == NULL)
39. {
40. "Fgets Error!\n");
41. return -1;
42. }
43.
44. '\0'; //fgets函数读取的最后一个字符为换行符,此处将其替换为'\0'
45.
46. "Input Command Is [ %s ]\n",commd);
47.
48. if(strncmp(commd,"help",4) == 0) //比较两个字符串前4个字节,若相等则返回0
49. {
50. commd_help();
51. else if(strncmp(commd, "exit",4) == 0)
52. {
53. commd_exit();
54. //结束进程
55. else if(strncmp(commd, "ls" , 2) == 0)
56. {
57. commd_ls(addr, commd);
58. else if(strncmp(commd, "get" , 3) == 0)
59. {
60. commd_get(addr, commd);
61. else if(strncmp(commd, "put", 3) ==0 )
62. {
63. commd_put(addr, commd);
64. else
65. {
66. "Command Is Error!Please Try Again!\n");
67. }
68.
69. }
70. return 0;
71. }
72.
73. void commd_help()
74. {
75.
76. "\n=------------------- Welcome to Use the Ftp ----------------=\n");
77. "| |\n");
78. "| help : Display All Command for the Server |\n");
79. "| |\n");
80. "| exit: Quit The Sever |\n");
81. "| |\n");
82. "| ls : Display All file On the Ftp Server |\n");
83. "| |\n");
84. "| get <file>: Download FIle from the Ftp Server |\n");
85. "| |\n");
86. "| put <file>: Upload FIle to the Ftp Server |\n");
87. "| |\n");
88. "=-----------------------------------------------------------=\n");
89.
90. return ;
91. }
92.
93. void commd_exit()
94. {
95. "Byte!\n");
96. }
97.
98. void commd_ls(struct sockaddr_in addr, char *commd)
99. {
100. int sockfd;
101. //创建套接字
102. if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
103. {
104. "Socket Error!\n");
105. exit(1);
106. }
107.
108. if(connect(sockfd, (SA *)&addr, sizeof(addr)) < 0)
109. {
110. "Connect Error!\n");
111. exit(1);
112. }
113. //将commd指向的内容写入到sockfd所指的文件中,此处即指套接字
114. if(write(sockfd, commd, N) < 0)
115. {
116. "Write Error!\n");
117. exit(1);
118. }
119.
120. while(read(sockfd, commd, N) > 0) //从sockfd中读取N字节内容放入commd中,
121. //返回值为读取的字节数
122. " %s ",commd);
123. }
124. "\n");
125.
126. close(sockfd);
127. return ;
128. }
129. /**************************************************/
130. /*函数功能:实现文件的下载 */
131. /**************************************************/
132. void commd_get(struct sockaddr_in addr, char *commd)
133. {
134. int fd;
135. int sockfd;
136. char buffer[N];
137. int nbytes;
138. //创建套接字,并进行错误检测
139. if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
140. {
141. "Socket Error!\n");
142. exit(1);
143. }
144. //connect函数用于实现客户端与服务端的连接,此处还进行了错误检测
145. if(connect(sockfd, (SA *)&addr, sizeof(addr)) < 0)
146. {
147. "Connect Error!\n");
148. exit(1);
149. }
150. //通过write函数向服务端发送数据
151. if(write(sockfd, commd, N) < 0)
152. {
153. "Write Error!At commd_get 1\n");
154. exit(1);
155. }
156. //利用read函数来接受服务器发来的数据
157. if(read(sockfd, buffer, N) < 0)
158. {
159. "Read Error!At commd_get 1\n");
160. exit(1);
161. }
162. //用于检测服务器端文件是否打开成功
163. if(buffer[0] =='N')
164. {
165. close(sockfd);
166. "Can't Open The File!\n");
167. return ;
168. }
169. //open函数创建一个文件,文件地址为(commd+4),该地址从命令行输入获取
170. if((fd=open(commd+4, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
171. {
172. "Open Error!\n");
173. exit(1);
174. }
175. //read函数从套接字中获取N字节数据放入buffer中,返回值为读取的字节数
176. while((nbytes=read(sockfd, buffer, N)) > 0)
177. {
178. //write函数将buffer中的内容读取出来写入fd所指向的文件,返回值为实际写入的字节数
179. if(write(fd, buffer, nbytes) < 0)
180. {
181. "Write Error!At commd_get 2");
182. }
183. }
184.
185. close(fd);
186. close(sockfd);
187.
188. return ;
189.
190. }
191. /**************************************************/
192. /*函数功能:实现文件的上传 */
193. /**************************************************/
194. void commd_put(struct sockaddr_in addr, char *commd)
195. {
196. int fd;
197. int sockfd;
198. char buffer[N];
199. int nbytes;
200. //创建套接字
201. if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
202. {
203. "Socket Error!\n");
204. exit(1);
205. }
206. //客户端与服务端连接
207. if(connect(sockfd, (SA *)&addr, sizeof(addr)) < 0)
208. {
209. "Connect Error!\n");
210. exit(1);
211. }
212. //从commd中读取N字节数据,写入套接字中
213. if(write(sockfd, commd, N)<0)
214. {
215. "Wrtie Error!At commd_put 1\n");
216. exit(1);
217. }
218. //open函数从(commd+4)中,读取文件路径,以只读的方式打开
219. if((fd=open(commd+4, O_RDONLY)) < 0)
220. {
221. "Open Error!\n");
222. exit(1);
223. }
224. //从fd指向的文件中读取N个字节数据
225. while((nbytes=read(fd, buffer, N)) > 0)
226. {
227. //从buffer中读取nbytes字节数据,写入套接字中
228. if(write(sockfd, buffer, nbytes) < 0)
229. {
230. "Write Error!At commd_put 2");
231. }
232. }
233.
234. close(fd);
235. close(sockfd);
236.
237. return ;
238. }下面server.c中后面有些注释没加,感觉功能上跟client相似,就没加,可以参看前面的
1. //server.c
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <unistd.h>
5. #include <string.h>
6. #include <sys/types.h>
7. #include <sys/socket.h>
8. #include <arpa/inet.h>
9. #include <dirent.h>
10. #include <fcntl.h>
11.
12. #define N 256
13.
14. typedef struct sockaddr SA;
15.
16. void commd_ls(int);
17. void commd_get(int, char *);
18. void commd_put(int, char *);
19.
20. int main(int arg, char *argv[])
21. {
22. int ser_sockfd,cli_sockfd;
23. struct sockaddr_in ser_addr,cli_addr;
24. int ser_len, cli_len;
25. char commd [N];
26. //将commd所指向的字符串的前N个字节置为0,包括'\0'
27.
28. if((ser_sockfd=socket(AF_INET, SOCK_STREAM, 0) ) < 0)
29. {
30. "Sokcet Error!\n");
31. return -1;
32. }
33.
34. sizeof(ser_addr));
35. ser_addr.sin_family = AF_INET;
36. //在TCP连接中,此处类似于自动获取ip地址
37. //在绑定ip时,自动选择ip地址
38. ser_addr.sin_port = htons ( 8989 );
39. sizeof(ser_addr);
40. //将ip地址与套接字绑定
41. if((bind(ser_sockfd, (SA *)&ser_addr, ser_len)) < 0)
42. {
43. "Bind Error!\n");
44. return -1;
45. }
46. //服务器端监听
47. if(listen(ser_sockfd, 5) < 0)
48. {
49. "Linsten Error!\n");
50. return -1;
51. }
52.
53. sizeof(cli_addr));
54. sizeof(cli_addr);
55.
56. while(1)
57. {
58. "server_ftp>");
59. //服务器端接受来自客户端的连接,返回一个套接字,此套接字为新建的一个,并将客户端的地址等信息存入cli_addr中
60. //原来的套接字仍处于监听中
61. if((cli_sockfd=accept(ser_sockfd, (SA *)&cli_addr, &cli_len)) < 0)
62. {
63. "Accept Error!\n");
64. exit(1);
65. }
66. //由套接字接收数据时,套接字把接收的数据放在套接字缓冲区,再由用户程序把它们复制到用户缓冲区,然后由read函数读取
67. //write函数同理
68. if(read(cli_sockfd, commd, N) < 0) //read函数从cli_sockfd中读取N个字节数据放入commd中
69. {
70. "Read Error!\n");
71. exit(1);
72. }
73.
74. "recvd [ %s ]\n",commd);
75.
76. if(strncmp(commd,"ls",2) == 0)
77. {
78. commd_ls(cli_sockfd);
79. else if(strncmp(commd,"get", 3) == 0 )
80. {
81. commd_get(cli_sockfd, commd+4);
82. else if(strncmp(commd, "put", 3) == 0)
83. {
84. commd_put(cli_sockfd, commd+4);
85. else
86. {
87. "Error!Command Error!\n");
88. }
89. }
90.
91. return 0;
92. }
93.
94. void commd_ls(int sockfd)
95. {
96. DIR * mydir =NULL;
97. struct dirent *myitem = NULL;
98. char commd[N] ;
99. bzero(commd, N);
100. //opendir为目录操作函数,类似于open函数
101. //mydir中存有相关目录的信息(有待学习)
102. if((mydir=opendir(".")) == NULL)
103. {
104. "OpenDir Error!\n");
105. exit(1);
106. }
107.
108. while((myitem = readdir(mydir)) != NULL)
109. {
110. if(sprintf(commd, myitem->d_name, N) < 0)
111. {
112. "Sprintf Error!\n");
113. exit(1);
114. }
115.
116. if(write(sockfd, commd, N) < 0 )
117. {
118. "Write Error!\n");
119. exit(1);
120. }
121. }
122.
123. closedir(mydir);
124. close(sockfd);
125.
126. return ;
127. }
128.
129. void commd_get(int sockfd, char *filename)
130. {
131. int fd, nbytes;
132. char buffer[N];
133. bzero(buffer, N);
134.
135. "get filename : [ %s ]\n",filename);
136. if((fd=open(filename, O_RDONLY)) < 0)
137. {
138. "Open file Error!\n");
139. 'N';
140. if(write(sockfd, buffer, N) <0)
141. {
142. "Write Error!At commd_get 1\n");
143. exit(1);
144. }
145. return ;
146. }
147.
148. 'Y'; //此处标示出文件读取成功
149. if(write(sockfd, buffer, N) <0)
150. {
151. "Write Error! At commd_get 2!\n");
152. close(fd);
153. exit(1);
154. }
155.
156. while((nbytes=read(fd, buffer, N)) > 0)
157. {
158. if(write(sockfd, buffer, nbytes) < 0)
159. {
160. "Write Error! At commd_get 3!\n");
161. close(fd);
162. exit(1);
163. }
164. }
165.
166. close(fd);
167. close(sockfd);
168.
169. return ;
170. }
171.
172. void commd_put(int sockfd, char *filename)
173. {
174. int fd, nbytes;
175. char buffer[N];
176. bzero(buffer, N);
177.
178. "get filename : [ %s ]\n",filename);
179. if((fd=open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
180. {
181. "Open file Error!\n");
182. return ;
183. }
184.
185. while((nbytes=read(sockfd, buffer, N)) > 0)
186. {
187. if(write(fd, buffer, nbytes) < 0)
188. {
189. "Write Error! At commd_put 1!\n");
190. close(fd);
191. exit(1);
192. }
193. }
194.
195. close(fd);
196. close(sockfd);
197.
198. return ;
199. }
- 一、 服务器server的写法:1. 创建 socket 套接字:网络编程接口 socket(family = AF_INET , type = SOCKET_STREM,proto = 0, fileno = None) 提供了多种socket family。AF_INET 是默认的family,需要绑定IP和端口。 127.0.0.1是一个特殊的IP地址,表示本机地址。如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。不同的协议family使用的地址形式不同,通常来说我们使用的是AF_INET-----地址是以(IP,PORT)形式存。在type 类型,我们常用的有两种 SOCKET_STREM ,流式套接字,表示基于连接的TCP套接字,SOCKET_DGRAM, 数据报套接字,基于无连接的(UDP)接口。 如果不设定,默认就是 SOCKET_STREM 。2. bind 绑定 使用方法:socket.bind(address) 把套接字绑定在 address 上,address的形式 根据 family 来设定。不管是client还是server,创建socket的时候都是通过一个本地的文件来进行的。 3. listen 监听 使用方法:socket.listen([backlog]) 使能 socket 接收连接请求,listen(self,backlog = None) ,backlog需要大于0,指定了可以缓存的连接请求的数量。4. accept 接受请求连接在等待一个即将来临的连接,会返回一个代表连接的新的socket,还会返回一个地址(host和port),可以用两个东西接收,前一个代表新的socket,后一个就是接收地址。写的方法:connet_socket,client_addr = srv.accept() connet_socket就是新的socket,然后connet_socket开始接下来的传输。connet_socket,client_addr,前者表示接收的新的socket,后者就是地址,具体看程序第10行。5. 接收数据 使用方法:socket.recv(bufsize[,flags]) 从 socket 中接收数据,返回的是 bytes ,是接收到的内容。bufsize指定了一次最多接收多少个数据,如果没有数据接收,程序会阻塞,一直到有数据或者远程终端断开连接.6. 发送数据 使用方法:socket.send(bytes[, flags]) 你的socket必须和远程的socket建立了联系,返回值是发送的数量,可以判断你的数据是否发送完毕,如果没有,继续send余下来的数据
import socket
hostname = '127.0.0.1' #设置主机名
port = 6666 #设置端口号 要确保这个端口号没有被使用,可以在cmd里面查看
addr = (hostname,port)
srv = socket.socket() #创建一个socket
srv.bind(addr)
srv.listen( 5 )
print ( "waitting connect" )
while True :
connect_socket,client_addr = srv.accept()
print (client_addr)
recevent = connect_socket.recv( 1024 )
print ( str (recevent,encoding = 'gbk' ))
connect_socket.send.send(bytes( "你好,数据传输完成,这里是gaby-yan--server" ,encoding = 'gbk' ))
connect_socket.close()二、 客户端client的写法:
客户端的写法相对比较简单,只有
1.创建socket
2. 建立连接 connect
3. 发送 send
4. 接收recv
这是由于他们的传递编程框架不同造成的,如图。

import socket
hostname = '127.0.0.1'
port = 7777
addr = (hostname,port)
clientsock = socket.socket() ## 创建一个socket
clientsock.connect(addr) # 建立连接
say = input ( "输入你想传送的消息:" )
clientsock.send(bytes(say,encoding = 'gbk' )) #发送消息
recvdata = clientsock.recv( 1024 ) #接收消息 recvdata 是bytes形式的
print ( str (recvdata,encoding = 'gbk' )) # 我们看不懂bytes,所以转化为 str
clientsock.close()注意:先运行server的代码,再运行client的代码。
















