1、通訊端概述
通訊端的本意是插座,在網路中用來描述電腦中不同程式與其他電腦程式的通訊方式。
常用的通訊端類型有3種:
1)流通訊端(SOCK——STREAM):使用了連線導向的可靠的資料通訊方式,即TCP通訊端;
2)資料通訊端(Raw Sockets):使用了不連線導向的資料轉送方式,即UDP通訊端;
3)原始通訊端(SOCK——RAW):沒有經過處理的IP資料包,可以根據自己程式的要求進行封裝。 2、常用函數
1、建立通訊端函數:成功時返迴文件描述符,失敗時返回-1
int socket(int domain,int type,int protocol);//參數domain用於指定建立通訊端所使用的協議族(可取AF_UNIX,AF_INET,AF_INTE6)//參數type指定通訊端的類型(可取SOCK_STREAM,SOCK_DGRAM,SOCK_RAW)//參數protocol通常設定為0
2、在指定通訊端上建立連結函數:成功時返回0,失敗時返回-1
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);//參數sockfd是一個由函數socket建立的通訊端//參數serv_addr是一個地址結構,指定伺服器的IP地址和連接埠號碼//參數addrlen為參數serv_addr的長度
3、將一個通訊端和某個連接埠綁定在一起的函數:成功時返回0,失敗時返回-1
int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);//一般只有伺服器端的程式調用,參數my_addr指定了sockfd將綁定到的本地//地址,可以將參數my_addr的sin_addr設定為INADDR_ANY而不是某個確定//IP地址就可以綁定到任何網路介面。
4、把通訊端轉化為被動監聽函數:成功時返回0,失敗時返回-1
int listen(int s,int backlog);//參數s為通訊端,參數backlog指定連結請求隊列的最大長度;
5、接收串連請求函數:成功時返迴文件描述符,失敗時返回-1
int accept(int s,struct sockaddr *addr,socklen_t *addrlen);//參數s是由函數socket建立,經函數bind綁定到本地某一連接埠上,然後通過//函數listen轉化而來的監聽通訊端//參數addr用來儲存發起串連請求的主機的地址和連接埠//參數addrlen是addr所指向的結構體的大小
6、在TCP通訊端上發送資料函數:有串連
包含3要素:通訊端s,待發資料msg,資料長度len
ssize_t send(int s,const void *msg,size_t len,int flags);//函數只能對處於串連狀態的通訊端使用,參數s為已建立好串連的通訊端描述//符,即accept函數的傳回值//參數msg指向存放待發送資料的緩衝區//參數len為待發送資料的長度,參數flags為控制選項,一般設定為0
7、在TCP通訊端上接收資料函數:有串連
包含3要素:通訊端s,接收緩衝區buf,長度len
ssize_t recv(int s,void *buf,size_t len,int flags);//函數recv從參數s所指定的通訊端描述符(必須是連線導向的通訊端)上接收//資料並儲存到參數buf所指定的緩衝區//參數len則為緩衝區長度,參數flags為控制選項,一般設定為0
8、在UCP通訊端上發送資料函數:無串連
ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);//函數功能與函數send類似,但函數sendto不需要通訊端處於串連狀態,所以//該函數通常用來發送UDP資料,同時因為是不需連線的通訊端,在使用sendto時//需要指定資料的目的地址,參數msg指向待發送資料的緩衝區。//參數len指定了待發送資料的長度//參數flags是控制選項,含義與send函數中的一致//參數to用於指定目的地址,目的地址的長度由tolen指定
9、在UDP通訊端上接收資料函數:無串連
ssize_t recvfrom(int s ,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);//與函數recv功能類似,只是函數recv只能用於連線導向的通訊端,而函數//recvfrom沒有此限制,可以用於從不需連線的通訊端上接收資料//參數buf指向接收緩衝區//參數len指定了緩衝區的大小//參數flags是控制選項,含義與recv中的一致//如果參數from非空,且該通訊端不是連線導向的,則函數recvfrom返回時,//參數from中將儲存資料的源地址//參數fromlen在調用recvfrom前為參數from的長度,調用recvfrom後將//儲存from的實際大小
10、關閉通訊端函數:
int close(int fd);//參數fd為一個通訊端描述符;
11、多工函數:
int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);//參數n是需要監視的檔案描述符數//參數readfds指定需要監視的可讀檔案描述符集合//參數writefds指定需要監視的可寫檔案描述符集合//參數exceptfds指定需要監視的異常檔案描述符的集合//參數timeout指定了阻塞的時間
3、伺服器端通訊端(接收串連請求的通訊端)建立過程
第一步:調用socket函數建立通訊端。
第二步:調用bind函數分配IP地址和連接埠號碼。
第三部:調用listen函數轉為可接收請求狀態。
第四步:調用accept函數受理串連請求。
另外還有read/write,以及close。 4、用戶端通訊端(發送串連請求的通訊端)建立過程
只有兩步:
1、調用socket函數建立通訊端。
2、調用connect函數向伺服器端發送串連請求。
另外還有read/write,以及close。 5、Linux的檔案操作
檔案描述符:是系統自動分配給檔案或通訊端的整數。
每當組建檔案或通訊端,作業系統就會自動返回給我們一個整數。這個整數就是檔案描述符,即建立的檔案或通訊端的別名,方便稱呼而已。檔案描述符在Windows中又稱為控制代碼。
1、開啟檔案:
2、關閉檔案或通訊端:
3、將資料寫入檔案:
4、讀取檔案中的資料:
註:ssize_t = signed int, size_t = unsigned int,都是通過typedef聲明,為基礎資料型別 (Elementary Data Type)取的別名。既然已經有了基礎資料型別 (Elementary Data Type),那麼為什麼還需要為它取別名呢。是因為目前普遍認為int是32位的,而過去16位作業系統時代,int是16位的。根據系統的不同,時代的變化,基礎資料型別 (Elementary Data Type)的表現形式也隨著變化的。如果為基礎資料型別 (Elementary Data Type)取了別名,以後要修改,也就只需要修改typedef聲明即可,這將大大減少代碼變動。 6、伺服器端執行個體
伺服器端受理串連請求的程式。編譯並運行該程式,建立等待串連請求的伺服器端。
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>void error_handling(char *message);int main(int argc, const char * argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; char message[] = "Hello World!"; if(argc != 2) { printf("Usage:%s <port>\n", argv[0]); exit(1); } //(1)調用socket函數建立通訊端 serv_sock = socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1) error_handling("socket() error"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); //(2)調用bind函數分配IP地址和連接埠號碼 if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind() error"); //(3)調用listen函數轉為可接收請求狀態 if(listen(serv_sock, 5) == -1) error_handling("listen() error"); clnt_addr_size = sizeof(clnt_addr); //(4)調用accept函數受理串連請求.沒有串連請求時調用不會返回,直到有請求 clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size); if(clnt_sock == -1) error_handling("accept() error"); //(5)write函數用於傳輸資料。執行到此行說明已有了串連請求 //向檔案描述符為clnt_sock的用戶端檔案中傳輸message中的資料 write(clnt_sock, message, sizeof(message)); close(clnt_sock);//(6) close(serv_sock);//(7) return 0;}void error_handling(char *message){ fputs(message, stderr); fputc('\n', stderr); exit(1);}
7、用戶端執行個體
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>void error_handling(char *message);int main(int argc, const char * argv[]){ int sock; struct sockaddr_in serv_addr; char message[30]; int str_len; if(argc != 3) { printf("Usage: %s <IP> <port>\n", argv[0]); exit(1); } //(1)建立通訊端.但此時並不馬上分為用戶端或伺服器端 sock = socket(PF_INET, SOCK_STREAM, 0); if(sock == -1) error_handling("socket() error"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); //(2)調用connect函數向伺服器端發送串連請求.此時確定為用戶端 if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("connect() error"); //(3)調用read函數向自身聲明的message數組中儲存資料 str_len = read(sock, message, sizeof(message) - 1); if(str_len == -1) error_handling("read() error"); printf("Message from server: %s \n", message); close(sock);//(4) return 0;}void error_handling(char *message){ fputs(message, stderr); fputc('\n', stderr); exit(1);}
7、基本客戶/伺服器通訊端圖形展示
參考:《TCP/IP網路編程》及《Unix網路編程卷1》