最近學習網路編程,做了一些筆記,簡要、明確,方便自己查看,也希望能讓更多人看到,網路編程的大概過程就是這樣。 1、網路編程中接受(server)串連請求的通訊端建立過程
第一步:調用socket函數建立通訊端
第二步:調用bind函數分配IP地址和連接埠號碼
第三步:調用listen函數轉為可接收請求狀態
第四步:調用accept函數受理串連請求 2、伺服器端建立的通訊端又稱為伺服器端通訊端或監聽通訊端 3、用戶端只有“調用socket函數建立通訊端”和“調用connect函數向伺服器端發送串連請求”兩個步驟 4、檔案描述符
每當組建檔案或通訊端,作業系統將返回分配給它們的整數。這個整數就將成為程式員和作業系統之間良好溝通的渠道。實際上,檔案描述符只不過是為了方便稱呼作業系統建立的檔案或者通訊端而賦予的數而已。
檔案描述符有時也稱為檔案控制代碼,但“控制代碼”主要是Windows中的術語。 5、協議
簡言之,協議就是為了完成資料交換而約定好的約定。 6、建立通訊端
#include <sys/socket.h>int socket(int domain, int type, int protocol) -> 成功時返迴文件描述符,失敗時返回-1
domain 通訊端中使用的協議簇(Protocol Family)資訊 type 通訊端資料轉送類型資訊 protocol 電腦間通訊中使用的協議資訊
協議簇
| 名稱 |
協 議 簇 |
| PF_INET |
IPv4互連網協議簇 |
| PF_INET6 |
IPv6互連網協議簇 |
| PF_LOCAL |
本地通訊的UNIX協議簇 |
| PF_PACKET |
底層通訊端的協議簇 |
| PF_IPX |
IPX Novell協議簇 |
通訊端中實際採用的最終協議資訊是通過socket函數的第三個參數傳遞的。在指定的協議簇範圍內通過第一個參數決定第三個參數。 通訊端類型(type)
通訊端類型指的是通訊端的資料轉送方式,通過socket函數的第二個參數傳遞,只有這樣才能決定建立的通訊端的資料轉送方式。
通訊端類型1:連線導向的通訊端(SOCKET_STREAM)
這種方式的特徵: 傳輸過程中資料不會消失 按序傳輸資料 傳輸的資料不存在資料邊界
一句話概括連線導向的通訊端:“可靠的、按序傳遞的、基於位元組的連線導向的資料轉送方式的通訊端”
通訊端類型2:面向訊息的通訊端(SOCK_DGRAM)
這種方式的特徵: 強調快速傳輸而非傳輸順序 傳輸的資料可能丟失也可能損毀 傳輸的資料有資料邊界 限制每次傳輸的資料大小
面向訊息的通訊端比連線導向的通訊端具有更快的傳輸速度,但無法避免資料丟失或損毀。
“不可靠的、不按序傳遞的、以資料的高速傳輸為目的的通訊端” 協議的最終選擇(第三個參數)
已經通過socket函數的前兩個參數傳遞了協議簇資訊和通訊端資料轉送方式,一般情況下,這兩個參數已經可以建立所需通訊端。所以大部分情況下可以向第三個參數傳遞0,除非遇到以下這種情況:
“同一協議簇中存在多個資料轉送方式相同的協議”
建立如下通訊端:“IPv4協議簇中連線導向的通訊端”,參數PF_INET指IPv4網路通訊協定簇,SOCK_STREAM是連線導向的資料轉送。滿足這兩個條件的協議只有IPPROTO_TCP,因此:
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
下面建立如下通訊端:“IPv4協議簇中面向訊息的通訊端”
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 7、地址族與資料序列
IP是為了收髮網絡資料而分配給電腦的值。連接埠號碼並非賦予電腦的值,而是為了區分程式中建立的通訊端而分配給通訊端的序號。 網路地址(Internet Address)
IP地址分為兩類:IPv4和IPv6
總結:“IP是用於定位到特定的主機,連接埠號碼是用於定位到某個主機上特定的程式。”
連接埠號碼不允許重複,但是TCP通訊端和UDP通訊端不會公用連接埠號碼,所以允許重複。例如某TCP通訊端使用9190號連接埠,則其他的TCP通訊端不能在使用9190,但是UDP通訊端可以使用。
總之,資料轉送目標地址同時包含IP地址和連接埠號碼,只有這樣,資料才會被傳輸到最終的應用程式。 地址資訊的表示
應用程式中使用的IP地址和連接埠號碼以結構體的形式給出了定義
struct sockaddr_in{ sa_family_t sin_family; //地址族(Address Family) uint16_t sin_port; //16位TCP/UDP連接埠號碼 struct in_addr sin_addr; //32位IP地址 char sin_zero[8]; //不使用}
該結構體中提到的另一個結構體in_addr定義如下,它用來存放32位IP地址
struct in_addr{ In_addr_t s_addr; //32位ipv4地址}
網路位元組序跟大端序相同
伺服器端常見通訊端初始化過程
int serv_sock;struct sockaddr_in serv_addr;char *serv_port = "9190";//建立伺服器端通訊端(監聽通訊端)serv_sock=socket(PF_INET,SOCK_STREAM,0);//地址資訊初始化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(serv_addr));//分配地址資訊bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
8、基於TCP的伺服器端和用戶端
根據資料轉送方式的不同,基於網路通訊協定的通訊端一般分為TCP通訊端和UDP通訊端。因為TCP通訊端是連線導向的,因此又稱為基於流(stream)的通訊端。
伺服器端處於等待串連請求狀態是指:用戶端請求串連時,受理串連前一直使請求串連處於等待狀態。
hello world伺服器端代碼,hello_server.c
int main(int argc, 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", agrv[0]); exit(1); } 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])); if(bind(serv_sock, (struct sockaddr*)serv_addr, sizeof(serv_addr))==-1) error_handling("bind() error"); if(listen(serv_sock, 5)==-1) error_handling("listen() error"); clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size); if(clnt_sock == -1){ error_handling("accept() error"); } write(clnt_sock, message, sizeof(message)); close(clnt_sock); close(serv_sock); return 0;}void error_handling(char *message){ fputs(message, stderr); fputc('\n', stderr); exit(1);}
Hello World用戶端,hello_world.c
int main(int argc, 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(0); } 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])); if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) error_handling("connect() error"); 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); return 0;}void error_handling(char *message){ fputs(message, stderr); fputc('\n',stderr); exit(1);}
9、基於UDP的伺服器端/用戶端
UDP通訊端的特點
通過信件說明UDP的工作原理,它與UDP特性完全相符。寄信前應先在信封上填好寄信人和收信人的地址,之後貼上郵票放進郵筒即可。當然,信件的特點使我們無法確認對方是否收到。另外,郵寄過程中也可能發生信件丟失的情況。也就是說,信件是一種不可靠的傳輸方式。與之類似,UDP提供的同樣是不可靠的Data Transmission Service。
流量控制是區分UDP和TCP的最重要的標誌。TCP的生命在於流量控制。TCP在不可靠的IP層進行流量控制,而UDP就沒有這種流量控制機制。
UDP最重要的作用就是根據連接埠號碼將傳到主機的資料包交付給最終的UDP通訊端。
TCP比UDP慢的原因通常有以下兩點: 收發資料前後進行的串連設定及清除過程 收發資料過程中為保證可靠性而添加的流量控制 實現基於UDP的伺服器/用戶端 UDP中伺服器端和用戶端沒有串連
UDP伺服器端/用戶端不像TCP那樣在串連狀態下交換資料,因此與TCP不同,無需經過串連過程。也就是說,不必調用TCP串連過程中調用的listen函數和accept函數。UDP中只有建立通訊端和資料交換過程。 UDP伺服器端和用戶端均只需1個通訊端
TCP中,通訊端之間應該是一對一的關係。若要向10個用戶端提供服務,則除了守門的伺服器通訊端外,還需要10個伺服器端通訊端。但在UDP中,不管是伺服器端還是用戶端都只需要1個通訊端。 基於UDP的資料I/O函數
建立好TCP通訊端後,傳輸資料時無需再添加地址資訊。因為TCP通訊端將保持與對方通訊端的串連。換言之,TCP通訊端知道目標地址資訊。但UDP通訊端不會保持串連狀態(UDP通訊端只有簡單的郵筒功能),因此每次傳輸資料都要添加目標地址資訊。這相當於寄信前在信件中添寫地址。
傳輸資料時調用的UDP相關函數
#include<sys/socket.h>ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen); ->成功時返回傳輸的位元組數,失敗時返回-1 sock 用於傳輸資料的UDP通訊端檔案描述符 buff 儲存待傳輸資料的緩衝地址值 nbytes 待傳輸的資料長度,以位元組為單位 flags 可選項參數,若沒有則傳遞0 to 存有目標地址資訊的sockaddr結構體變數的地址值 addrlen 傳遞給參數to的地址值結構體變數長度
接收UDP資料的函數
#include<sys/socket.h>ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); ->成功時返回接收的位元組數,失敗時返回-1
編寫UDP程式時最核心的部分就是上述兩個函數。 基於UDP的回聲伺服器端/用戶端
UDP和TCP不同,不存在請求串連和受理過程,因此在某種意義上無法明確區分伺服器端和用戶端,只是因其提供服務而稱為伺服器端。
uecho_server.c
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>#define BUF_SIZE 30void error_handing(char *message);int main(int argc, char *argv){ int serv_sock; char message[BUF_SIZE]; int str_len; socklen_t clnt_adr_sz; struct sockaddr_in serv_adr,clnt_adr; if(argc != 2){ printf("Usage: %s <port>\n",argv[0]); exit(1); } serv_sock = socket(PF_INET,SOCK_DGRAM, 0); if(serv_sock == -1) error_handling("UDP socket creation error"); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family=AF_INET; serv_adr.sin_addr.s_addr=htonl(INADDR_ANY); serv_adr.sin_port=htons(atoi(argv[1])); if(bind(serv_sock,, (struct sockaddr*)&serv)_adr, sizeof(serv_addr))==-1) error_handling("bind() error"); while(1){ clnt_adr_sz=sizeof(clnt_adr); str_len=recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz); } close(serv_sock); return 0;}void error_handling(char *message){ fputs(message, stderr); fputc('\n',stderr); exit(1);}
uecho_client.c 不存在connect函數調用
#include <"與uecho_server.c相同">#define BUF_SIZE 30void error_handling(char *message);int main(int argc, char *argv[]){ int sock; char message[BUF_SIZE]; int str_len; socklen_t adr_sz; struct sockaddr_in serv_adr, clnt_adr; if(argc != 3){ printf("Usage: %s <IP> <port>\n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_DGRAM, 0); if(sock ==-1){ error_handling("socket() error"); } memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family=AF_INET; serv_adr.sin_addr.s_addr=inet_addr(argv[1]); serv_addr.sin_port=atoi(argv[2])); while(1){ fputs("Inert message(q to quit): ",stdout); fgets(message, sizeof(message), stdin); if(!strcmp(message,"q\n") || !strcmp(message, "Q\n")) break; sendto(sock, message, strlen(message), 0, (struct sockadr*)&serv_adr, sizeof(serv_adr)); adr_sz=sizeof(from_adr); str_len=recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz); message[str_len]=0; printf("Message from server: %s", message); } close(sock); return 0}void error_handling(char *message){ fputs(message, stderr); fputc('\n',stderr); exit(1);}
TCP是在調用connect函數時自動分配IP地址和連接埠號碼的,而UDP呢,是在調用sendto函數時自動分配IP和連接埠號碼的。