在網絡上,通信服務都是采用C/S機制,也就是客戶端/服務器機制。流程可以參考下圖:
服務器端工作流程:
使用socket()函數創建服務器端通信套接口
使用bind()函數將創建的套接口與服務器地址綁定
使用listen()函數使服務器套接口做好接收連接請求准備
使用accept()接收來自客戶端由connect()函數發出的連接請求
根據連接請求建立連接后,使用send()函數發送數據,或者使用recv()函數接收數據
使用closesocket()函數關閉套接口(可以先用shutdown()函數先關閉讀寫通道)
客戶端程序工作流程:
使用socket()函數創建客戶端套接口
使用connect()函數發出也服務器建立連接的請求(調用前可以不用bind()端口號,由系統自動完成)
連接建立后使用send()函數發送數據,或使用recv()函數接收數據
使用closesocet()函數關閉套接口
下面介紹幾個函數的用法:
socket函數:int socket(int domain,int type,int protocol)
參數說明:
domain:指明協議族,也稱為協議域,是一個常值。
AF_INET:IPv4 協議
AF_INET6:IPv6 協議
AF_LOCAL/AF_UNIX:Unix協議域
AF_ROUTE: 路由套接字
AF_KE:密匙套接字
type:指明套接字的類型。
SOCK_STREA:字節流套接字
SOCK_DGRA:數據報套接字
SOCK_SEQPACKE:有序分組套接字
SOCK_RAW:原始套接字
protocol: 指明協議類型。一般為0,以選擇給定的domain和type組合的系統默認值。
IPPROTO_TCP:TCP傳輸協議
IPPROTO_UDP:UDP傳輸協議
IPPROTO_SCTP:SCTP傳輸協議
函數描述:
socket 函數在成功時返回一個小的非負整數值,與文件描述符類似,我們稱它為套接字
描述符,簡稱 sockfd。為了得到這個套接字描述符,我們只是指定了協議族(IPv4、IPv6
或Unix)和套接字類型(字節流、數據報或原始套接字)。我們並沒有指定本地跟遠程的
協議地址
bind函數:int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
將一個本地協議地址賦予一個套接字。對於網際網協議,協議地址是32位的IPv4地址和128
位的IPv6地址與16位的TCP或UDP端口號的組合。bind 函數主要用於服務器端,用來指定本地
主機的哪個網絡接口(IP,可以是INADDR_ANY,表示本地主機的任一網絡接口)可以接受客戶
端的請求,和指定端口號(即開啟的等待客戶來連接的進程)。
參數說明:
sockfd: socket 函數返回的套接字描述符。
myaddr、addrlen:指向一個套接字地址結構的指針和該結構的大小。
地址結構的一般采用sockadr_in結構體
struct sockaddr_in{
short int sin_family; #地址族
unsigned short int sin_port; #端口號
struct n_addr sin_addr; #IP地址
unsigned char sin_zeor[8]; #填充0保持與struct sockaddr同樣大小
}
listen函數:
int listen(int sockfd,int backlog)
listen函數的第一個參數即為要監聽的socket描述字,第二個參數為相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變為被動類型的,等待客戶的連接請求。
Connetc函數:
connect函數的第一個參數即為客戶端的socket描述字,第二參數為服務器的socket地址,第三個參數為socket地址的長度。客戶端通過調用connect函數來建立與TCP服務器的連接
accept函數:
int accept(int sockfd,struct sockaddr *addr, socketen_t *add_len)
- 參數 sockfd
- 參數 sockfd 就是上面解釋中的監聽套接字,這個套接字用來監聽一個端口,當有一個客戶與服務器連接時,它使用這個一個端口號,而此時這個端口號正與這個套接字關聯。當然客戶不知道套接字這些細節,它只知道一個地址和一個端口號。
- 參數 addr
- 這是一個結果參數,它用來接受一個返回值,這返回值指定客戶端的地址,當然這個地址是通過某個地址結構來描述的,用戶應該知道這一個什么樣的地址結構。如果對客戶的地址不感興趣,那么可以把這個值設置為 NULL 。
- 參數 len
- 如同大家所認為的,它也是結果的參數,用來接受上述 addr 的結構的大小的,它指明 addr 結構所占有的字節個數。同樣的,它也可以被設置為 NULL 。
如果accept成功返回,則服務器與客戶已經正確建立連接了,此時服務器通過accept返回的套接字來完成與客戶的通信。
服務器測的代碼如下:對於服務端來說,首先是建立socket然后綁定IP地址到socket上。然后開始監聽。
當接收到請求的時候,通過recv函數存儲在buf數組里面。並且通過send函數向對端發送消息
int server_function()
{
char *sendbuf="thanks";
char buf[256];
int s_fd,c_fd;
int s_len,c_len;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
s_fd=socket(AF_INET,SOCK_STREAM,0);
s_addr.sin_family=AF_INET;
s_addr.sin_addr.s_addr=htonl(INADDR_ANY);
s_addr.sin_port=PORT;
s_len=sizeof(s_addr);
bind(s_fd,(struct sockaddr *)&s_addr,s_len);
listen(s_fd,10);
while(1){
printf("please wait a moment\n");
c_len=sizeof(c_addr);
c_fd=accept(s_fd,(struct sockaddr *)&c_addr,(socklen_t *__restrict)&c_len);
recv(c_fd,buf,256,0);
buf[strlen(buf)+1]='\0';
printf("receve message:\n%s\n",buf);
send(c_fd,sendbuf,strlen(sendbuf),0);
close(c_fd);
}
}
客戶端的代碼如下:設置好連接的IP地址和端口后,通過connect發起連接。並通過send和recv函數進行發送和接收消息
int client_function()
{
char *buf="come on";
char rebuf[250];
int sockfd,len,newsockfd,len2;
struct sockaddr_in addr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port=PORT;
len=sizeof(addr);
newsockfd=connect(sockfd,(struct sockaddr *)&addr,len);
len2=strlen(buf);
send(sockfd,buf,len2,0);
sleep(5);
recv(sockfd,rebuf,40,0);
printf("the length of the rebuf is %d",strlen(rebuf));
rebuf[strlen(rebuf)+1]='\0';
printf("receive message:\n%s\n",rebuf);
close(sockfd);
return 0;
}
打開2個終端,分別運行服務器和客戶端的代碼。執行結果如下: