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信號,進程對該信號的默認處理是進程終止。
- //client.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <fcntl.h>
- #define N 256
- typedef struct sockaddr SA;
- void commd_help();
- void commd_exit();
- void commd_ls(struct sockaddr_in, char *);
- void commd_get(struct sockaddr_in , char *);
- void commd_put(struct sockaddr_in , char *);
- int main(int argc, char *argv[])
- {
- char commd[N];
- struct sockaddr_in addr;
- int len;
- bzero(&addr, sizeof(addr)); //將&addr中的前sizeof(addr)字節置為0,包括'\0'
- addr.sin_family = AF_INET; //AF_INET代表TCP/IP協議
- addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //將點間隔地址轉換為網絡字節順序
- addr.sin_port = htons(8989); //轉換為網絡字節順序
- len = sizeof(addr);
- while(1)
- {
- printf("ftp>");
- bzero(commd,N);
- //fgets函數從stdin流中讀取N-1個字符放入commd中
- if(fgets(commd,N,stdin) == NULL)
- {
- printf("Fgets Error!\n");
- return -1;
- }
- commd[strlen(commd)-1]='\0'; //fgets函數讀取的最后一個字符為換行符,此處將其替換為'\0'
- printf("Input Command Is [ %s ]\n",commd);
- if(strncmp(commd,"help",4) == 0) //比較兩個字符串前4個字節,若相等則返回0
- {
- commd_help();
- }else if(strncmp(commd, "exit",4) == 0)
- {
- commd_exit();
- exit(0); //結束進程
- }else if(strncmp(commd, "ls" , 2) == 0)
- {
- commd_ls(addr, commd);
- }else if(strncmp(commd, "get" , 3) == 0)
- {
- commd_get(addr, commd);
- }else if(strncmp(commd, "put", 3) ==0 )
- {
- commd_put(addr, commd);
- }else
- {
- printf("Command Is Error!Please Try Again!\n");
- }
- }
- return 0;
- }
- void commd_help()
- {
- printf("\n=------------------- Welcome to Use the Ftp ----------------=\n");
- printf("| |\n");
- printf("| help : Display All Command for the Server |\n");
- printf("| |\n");
- printf("| exit: Quit The Sever |\n");
- printf("| |\n");
- printf("| ls : Display All file On the Ftp Server |\n");
- printf("| |\n");
- printf("| get <file>: Download FIle from the Ftp Server |\n");
- printf("| |\n");
- printf("| put <file>: Upload FIle to the Ftp Server |\n");
- printf("| |\n");
- printf("=-----------------------------------------------------------=\n");
- return ;
- }
- void commd_exit()
- {
- printf("Byte!\n");
- }
- void commd_ls(struct sockaddr_in addr, char *commd)
- {
- int sockfd;
- //創建套接字
- if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
- {
- printf("Socket Error!\n");
- exit(1);
- }
- if(connect(sockfd, (SA *)&addr, sizeof(addr)) < 0)
- {
- printf("Connect Error!\n");
- exit(1);
- }
- //將commd指向的內容寫入到sockfd所指的文件中,此處即指套接字
- if(write(sockfd, commd, N) < 0)
- {
- printf("Write Error!\n");
- exit(1);
- }
- while(read(sockfd, commd, N) > 0) //從sockfd中讀取N字節內容放入commd中,
- { //返回值為讀取的字節數
- printf(" %s ",commd);
- }
- printf("\n");
- close(sockfd);
- return ;
- }
- /**************************************************/
- /*函數功能:實現文件的下載 */
- /**************************************************/
- void commd_get(struct sockaddr_in addr, char *commd)
- {
- int fd;
- int sockfd;
- char buffer[N];
- int nbytes;
- //創建套接字,並進行錯誤檢測
- if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
- {
- printf("Socket Error!\n");
- exit(1);
- }
- //connect函數用於實現客戶端與服務端的連接,此處還進行了錯誤檢測
- if(connect(sockfd, (SA *)&addr, sizeof(addr)) < 0)
- {
- printf("Connect Error!\n");
- exit(1);
- }
- //通過write函數向服務端發送數據
- if(write(sockfd, commd, N) < 0)
- {
- printf("Write Error!At commd_get 1\n");
- exit(1);
- }
- //利用read函數來接受服務器發來的數據
- if(read(sockfd, buffer, N) < 0)
- {
- printf("Read Error!At commd_get 1\n");
- exit(1);
- }
- //用於檢測服務器端文件是否打開成功
- if(buffer[0] =='N')
- {
- close(sockfd);
- printf("Can't Open The File!\n");
- return ;
- }
- //open函數創建一個文件,文件地址為(commd+4),該地址從命令行輸入獲取
- if((fd=open(commd+4, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
- {
- printf("Open Error!\n");
- exit(1);
- }
- //read函數從套接字中獲取N字節數據放入buffer中,返回值為讀取的字節數
- while((nbytes=read(sockfd, buffer, N)) > 0)
- {
- //write函數將buffer中的內容讀取出來寫入fd所指向的文件,返回值為實際寫入的字節數
- if(write(fd, buffer, nbytes) < 0)
- {
- printf("Write Error!At commd_get 2");
- }
- }
- close(fd);
- close(sockfd);
- return ;
- }
- /**************************************************/
- /*函數功能:實現文件的上傳 */
- /**************************************************/
- void commd_put(struct sockaddr_in addr, char *commd)
- {
- int fd;
- int sockfd;
- char buffer[N];
- int nbytes;
- //創建套接字
- if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
- {
- printf("Socket Error!\n");
- exit(1);
- }
- //客戶端與服務端連接
- if(connect(sockfd, (SA *)&addr, sizeof(addr)) < 0)
- {
- printf("Connect Error!\n");
- exit(1);
- }
- //從commd中讀取N字節數據,寫入套接字中
- if(write(sockfd, commd, N)<0)
- {
- printf("Wrtie Error!At commd_put 1\n");
- exit(1);
- }
- //open函數從(commd+4)中,讀取文件路徑,以只讀的方式打開
- if((fd=open(commd+4, O_RDONLY)) < 0)
- {
- printf("Open Error!\n");
- exit(1);
- }
- //從fd指向的文件中讀取N個字節數據
- while((nbytes=read(fd, buffer, N)) > 0)
- {
- //從buffer中讀取nbytes字節數據,寫入套接字中
- if(write(sockfd, buffer, nbytes) < 0)
- {
- printf("Write Error!At commd_put 2");
- }
- }
- close(fd);
- close(sockfd);
- return ;
- }
下面server.c中后面有些注釋沒加,感覺功能上跟client相似,就沒加,可以參看前面的
- //server.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <dirent.h>
- #include <fcntl.h>
- #define N 256
- typedef struct sockaddr SA;
- void commd_ls(int);
- void commd_get(int, char *);
- void commd_put(int, char *);
- int main(int arg, char *argv[])
- {
- int ser_sockfd,cli_sockfd;
- struct sockaddr_in ser_addr,cli_addr;
- int ser_len, cli_len;
- char commd [N];
- bzero(commd,N);//將commd所指向的字符串的前N個字節置為0,包括'\0'
- if((ser_sockfd=socket(AF_INET, SOCK_STREAM, 0) ) < 0)
- {
- printf("Sokcet Error!\n");
- return -1;
- }
- bzero(&ser_addr,sizeof(ser_addr));
- ser_addr.sin_family = AF_INET;
- ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);//在TCP連接中,此處類似於自動獲取ip地址
- //在綁定ip時,自動選擇ip地址
- ser_addr.sin_port = htons ( 8989 );
- ser_len = sizeof(ser_addr);
- //將ip地址與套接字綁定
- if((bind(ser_sockfd, (SA *)&ser_addr, ser_len)) < 0)
- {
- printf("Bind Error!\n");
- return -1;
- }
- //服務器端監聽
- if(listen(ser_sockfd, 5) < 0)
- {
- printf("Linsten Error!\n");
- return -1;
- }
- bzero(&cli_addr, sizeof(cli_addr));
- ser_len = sizeof(cli_addr);
- while(1)
- {
- printf("server_ftp>");
- //服務器端接受來自客戶端的連接,返回一個套接字,此套接字為新建的一個,並將客戶端的地址等信息存入cli_addr中
- //原來的套接字仍處於監聽中
- if((cli_sockfd=accept(ser_sockfd, (SA *)&cli_addr, &cli_len)) < 0)
- {
- printf("Accept Error!\n");
- exit(1);
- }
- //由套接字接收數據時,套接字把接收的數據放在套接字緩沖區,再由用戶程序把它們復制到用戶緩沖區,然后由read函數讀取
- //write函數同理
- if(read(cli_sockfd, commd, N) < 0) //read函數從cli_sockfd中讀取N個字節數據放入commd中
- {
- printf("Read Error!\n");
- exit(1);
- }
- printf("recvd [ %s ]\n",commd);
- if(strncmp(commd,"ls",2) == 0)
- {
- commd_ls(cli_sockfd);
- }else if(strncmp(commd,"get", 3) == 0 )
- {
- commd_get(cli_sockfd, commd+4);
- }else if(strncmp(commd, "put", 3) == 0)
- {
- commd_put(cli_sockfd, commd+4);
- }else
- {
- printf("Error!Command Error!\n");
- }
- }
- return 0;
- }
- void commd_ls(int sockfd)
- {
- DIR * mydir =NULL;
- struct dirent *myitem = NULL;
- char commd[N] ;
- bzero(commd, N);
- //opendir為目錄操作函數,類似於open函數
- //mydir中存有相關目錄的信息(有待學習)
- if((mydir=opendir(".")) == NULL)
- {
- printf("OpenDir Error!\n");
- exit(1);
- }
- while((myitem = readdir(mydir)) != NULL)
- {
- if(sprintf(commd, myitem->d_name, N) < 0)
- {
- printf("Sprintf Error!\n");
- exit(1);
- }
- if(write(sockfd, commd, N) < 0 )
- {
- printf("Write Error!\n");
- exit(1);
- }
- }
- closedir(mydir);
- close(sockfd);
- return ;
- }
- void commd_get(int sockfd, char *filename)
- {
- int fd, nbytes;
- char buffer[N];
- bzero(buffer, N);
- printf("get filename : [ %s ]\n",filename);
- if((fd=open(filename, O_RDONLY)) < 0)
- {
- printf("Open file Error!\n");
- buffer[0]='N';
- if(write(sockfd, buffer, N) <0)
- {
- printf("Write Error!At commd_get 1\n");
- exit(1);
- }
- return ;
- }
- buffer[0] = 'Y'; //此處標示出文件讀取成功
- if(write(sockfd, buffer, N) <0)
- {
- printf("Write Error! At commd_get 2!\n");
- close(fd);
- exit(1);
- }
- while((nbytes=read(fd, buffer, N)) > 0)
- {
- if(write(sockfd, buffer, nbytes) < 0)
- {
- printf("Write Error! At commd_get 3!\n");
- close(fd);
- exit(1);
- }
- }
- close(fd);
- close(sockfd);
- return ;
- }
- void commd_put(int sockfd, char *filename)
- {
- int fd, nbytes;
- char buffer[N];
- bzero(buffer, N);
- printf("get filename : [ %s ]\n",filename);
- if((fd=open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
- {
- printf("Open file Error!\n");
- return ;
- }
- while((nbytes=read(sockfd, buffer, N)) > 0)
- {
- if(write(fd, buffer, nbytes) < 0)
- {
- printf("Write Error! At commd_put 1!\n");
- close(fd);
- exit(1);
- }
- }
- close(fd);
- close(sockfd);
- return ;
- }
-
一、 服務器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余下來的數據
123456789101112131415import
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
這是由於他們的傳遞編程框架不同造成的,如圖。
12345678910111213import
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的代碼。
-