基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序
轉(zhuǎn)自:http://blog.csdn.net/apollon_krj/article/details/53398448#0-tsina-1-64987-397232819ff9a47a7b7e80a40613cfe1
所謂半雙工通信,即通信雙方都可以實(shí)現(xiàn)接發(fā)數(shù)據(jù),但是有一個限制:只能一方發(fā)一方收,之后交換收發(fā)對象。也就是所謂的阻塞式的通訊方式。
一、基本框架:
1、首先搞清我們進(jìn)行編程所處的的位置:
TCP編程,具有可靠傳輸?shù)奶匦?#xff0c;而實(shí)現(xiàn)可靠傳輸?shù)墓δ懿⒎俏覀儗⒁龅氖?#xff08;這些事),我們要做的就是在內(nèi)核實(shí)現(xiàn)的基礎(chǔ)上調(diào)用系統(tǒng)的API接口直接使用。所以我們所處的位置就是位于應(yīng)用層面與系統(tǒng)層面之間的。我覺得弄清這點(diǎn)是實(shí)現(xiàn)整個通信程序的重中之重。
2、弄清楚此次的目的:實(shí)現(xiàn)偽半雙工的通信
為什么說是“偽”半雙工通信,因為真正的半雙工是通信雙方都可以隨時接發(fā)數(shù)據(jù)(只是限制不能同時發(fā),也不能同時收,在同一時刻只能由一方發(fā)送,一方接收),而我們要實(shí)現(xiàn)的是“傻瓜式”的你一句我一句,因為不是全雙工,而類似于半雙工,我也不知道有沒有更準(zhǔn)確的說法,就暫且叫它“偽半雙工吧”!
3、TCP編程框架:
下面這張圖是很多博客中都使用到的一張流圖,其原圖都來自于UNIX網(wǎng)絡(luò)編程卷1:套接字聯(lián)網(wǎng)API 【史蒂文斯 (W.Richard Stevens)、芬納 (Bill Fenner) 、 魯?shù)婪?(Andrew M.Rudoff)著】這本書。核心思想都是一樣的,所以就直接貼上了:?
二、所用到的結(jié)構(gòu)體與函數(shù):
1、幾個結(jié)構(gòu)體:
(1)、IPV4套接字地址結(jié)構(gòu)體:
struct sockaddr_in{uint8_t sin_len;sa_famliy_t sin_fanliy;/*協(xié)議家族*/in_port_t sin_port;/*端口號*/struct in_addr sin_addr;/*IP地址,struct in_addr{in_addr_t s_addr;}*/char sin_zero[8]; };- 1
- 2
- 3
- 4
- 5
- 6
- 7
(2)、通用套接字地址結(jié)構(gòu)體:
struct sockaddr{uint8_t sa_len;sa_famliy sa_famliy;char sa_data[14]; };- 1
- 2
- 3
- 4
- 5
2、建基本框架所使用的函數(shù),這些函數(shù)都是系統(tǒng)調(diào)用(man 2 function),失敗都會設(shè)置一個errno錯誤標(biāo)志:
#include<sys/socket.h>- 1
(1)、socket:
int socket(int domain,int type, int protocol); /* 創(chuàng)建一個套接字: 返回值:創(chuàng)建成功返回一個文件描述符(0,1,2已被stdin、stdout、stderr占用,所以從3開始)失敗返回-1。 參數(shù):domain為協(xié)議家族,TCP屬于AF_INET(IPV4);type為協(xié)議類型,TCP屬于SOCK_STREAM(流式套接字);最后一個參數(shù)為具體的協(xié)議(IPPOOTO_TCP為TCP協(xié)議,前兩個已經(jīng)能確定該參數(shù)是TCP,所以也可以填0) */- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(2)、bind:
int bind(int sockfd,const struct sockaddr * addr,socklen_t addrlen); /* 將創(chuàng)建的套接字與地址端口等綁定 返回值:成功返回0,失敗返回-1. 參數(shù):sockfd為socket函數(shù)返回接受的文件描述符,addr為新建的IPV4套接字結(jié)構(gòu)體注意:定義若是使用struct sockaddr_in(IPV4結(jié)構(gòu)體)定義,但是該參數(shù)需要將struct sockaddr_in *類型地址強(qiáng)轉(zhuǎn)為struct sockaddr *類型(struct sockaddr是通用類型)。最后一個參數(shù)為該結(jié)構(gòu)體所占字節(jié)數(shù)。 */- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
(3)、listen:
int listen(int sockfd,int backlog); /* 對創(chuàng)建的套接字進(jìn)行監(jiān)聽,監(jiān)聽有無客戶請求連接 返回值:有客戶請求連接時,返回從已完成連接的隊列中第一個連接(即完成了TCP三次握手的的所有連接組成的隊列),否則處于阻塞狀態(tài)(blocking)。 參數(shù): sockfd依然為socket函數(shù)返回的文件描述符; blocklog為設(shè)定的監(jiān)聽隊列的長度??稍O(shè)為5、10等值但是不能大于SOMAXCONN(監(jiān)聽隊列最大長度) */- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
說說監(jiān)聽隊列(如下圖所示):?
監(jiān)聽隊列包括請求連接建立過程中的兩個子隊列:未完成連接的隊列和已完成連接的隊列。區(qū)分的標(biāo)志就是:是否完成TCP三次握手的過程。服務(wù)器從已完成連接的隊列中按照先進(jìn)先出(FIFO)的原則進(jìn)行接收。?
(4)、connect和accept:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(5)、send和recv:
ssize_t send(int sockfd,const void * buf,size_t len,int flags); /* 發(fā)送數(shù)據(jù) 返回值:成功返回發(fā)送的字符數(shù),失敗返回-1 參數(shù):buf為寫緩沖區(qū)(send_buf),len為發(fā)送緩沖區(qū)的大小,flags為一個標(biāo)志,如MSG_OOB表示有緊急帶外數(shù)據(jù)等 */ ssize_t recv(int sockfd,void *buf, size_t len, int flags); /* 接收數(shù)據(jù) 返回值參數(shù)與send函數(shù)相似 不過send是將buf中的數(shù)據(jù)向外發(fā)送,而recv是將接收到的數(shù)據(jù)寫到buf緩沖區(qū)中。 */- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(6)、close:
int close(int fd); /* 關(guān)閉套接字,類似于fclose,fd為要關(guān)閉的套接字文件描述符 失敗返回-1,成功返回0 */- 1
- 2
- 3
- 4
- 5
3、其它函數(shù):
(1)、字節(jié)序轉(zhuǎn)換函數(shù):
/*由于我們一般普遍所用的機(jī)器(x86)都是小端存儲模式或者說叫做小端字節(jié)序,而網(wǎng)絡(luò)傳輸中采用的是大端字節(jié)序,所以要進(jìn)行網(wǎng)絡(luò)通訊,就必須將進(jìn)行字節(jié)序的轉(zhuǎn)換,之后才可以進(jìn)行正常信息傳遞。*/uint32_t htonl(uint32_t hostlong);/*主機(jī)字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序*/ uint32_t ntohl(uint32_t netlong);/*網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序*/- 1
- 2
- 3
- 4
(2)、地址轉(zhuǎn)換函數(shù):
/*類似的原因,由于網(wǎng)絡(luò)傳輸是二進(jìn)制比特流傳輸,所以必須將我們的常用的點(diǎn)分十進(jìn)制的IP地址,與網(wǎng)絡(luò)字節(jié)序的IP源碼(二進(jìn)制形式)進(jìn)行互相轉(zhuǎn)換才可以將數(shù)據(jù)傳送到準(zhǔn)確的地址*/ int inet_aton(const char * cp,struct in_addr * inp);/*將字符串cp表示的點(diǎn)分十進(jìn)制轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序的二進(jìn)制形式后存儲到inp中*/ char * inet_ntoa(struct in_addr * in);/*將網(wǎng)絡(luò)字節(jié)序的二進(jìn)制形式轉(zhuǎn)換成點(diǎn)分十進(jìn)制的字符串形式,返回該字符串的首地址*/ in_addr_t inet_addr(const char * cp);/*與inet_aton的功能相同*/- 1
- 2
- 3
- 4
三、代碼實(shí)現(xiàn):
(1)、服務(wù)器(Server):服務(wù)器由于不知道客戶何時回請求建立連接,所以必須綁定端口之后進(jìn)行監(jiān)聽(Socket、Bind、Listen)?
(2)、客戶端(Client):客戶端只需要向服務(wù)器發(fā)起請求連接(connect),而不需要綁定與監(jiān)聽的步驟;?
(3)、請求連接由客戶端發(fā)起(主動打開),服務(wù)器接受連接請求(被動打開),會經(jīng)過TCP三次握手過程;而斷開連接服務(wù)器和客戶端都可以自行斷開,會經(jīng)過TCP四次揮手的過程。
1、服務(wù)器代碼(Server):
# include<sys/socket.h> # include<netinet/in.h> # include<arpa/inet.h> # include<signal.h> # include<assert.h> # include<stdio.h> # include<unistd.h> # include<string.h> # include<stdlib.h> # include<errno.h># define BUF_SIZE 1024//緩沖區(qū)大小宏定義int main (int argc,char * argv[])/*接收IP地址和端口號*/ {const char * ip = argv[1];int port = atoi(argv[2]);/*將輸入的端口號由字符串轉(zhuǎn)換為整數(shù)類型*//*結(jié)構(gòu)體定義與初始化*/struct sockaddr_in address;bzero(&address,sizeof(address));/*初始化清零,類似于memset函數(shù)*/address.sin_family = AF_INET;inet_pton(AF_INET,ip,&address.sin_addr);/*inet_pton是inet_aton的升級版,隨IPV6的出現(xiàn)而出現(xiàn)*/address.sin_port = htons(port);/*將小端字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序*/int sock = socket(PF_INET, SOCK_STREAM, 0);/*創(chuàng)建套接字*/assert(sock >= 0);int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));/*綁定IP地址、端口號等信息*/assert(ret != -1);ret = listen(sock,5);/*監(jiān)聽有無連接請求*/assert(ret != -1);struct sockaddr_in client;socklen_t client_addrlength = sizeof(client);int connfd = accept(sock,(struct sockaddr *)&client,&client_addrlength);/*從監(jiān)聽隊列中取出第一個已完成的連接*/char buffer_recv[BUF_SIZE]={0};char buffer_send[BUF_SIZE]={0};while(1){if(connfd < 0){printf("errno is : %d\n",errno);}else{memset(buffer_recv,0,BUF_SIZE);memset(buffer_send,0,BUF_SIZE);/*每次需要為緩沖區(qū)清空*/ret = recv(connfd, buffer_recv, BUF_SIZE-1, 0);if(strcmp(buffer_recv,"quit\n") == 0){printf("Communications is over!\n");break;}/*recv為quit表示客戶端請求斷開連接,退出循環(huán)*/printf("client:%s", buffer_recv);printf("server:");fgets(buffer_send,BUF_SIZE,stdin);send(connfd,buffer_send,strlen(buffer_send),0);if(strcmp(buffer_send,"quit\n") == 0){printf("Communications is over!\n");break;}/*send為quit表示服務(wù)器請求斷開連接,退出循環(huán)*/}}close(connfd);close(sock);return 0; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
2、客戶端代碼(Client):
# include<sys/socket.h> # include<netinet/in.h> # include<arpa/inet.h> # include<signal.h> # include<assert.h> # include<stdio.h> # include<unistd.h> # include<string.h> # include<stdlib.h>#define BUF_SIZE 1024int main (int argc,char * argv[]) {const char * ip = argv[1];int port = atoi(argv[2]);struct sockaddr_in server_address;bzero(&server_address,sizeof(server_address));server_address.sin_family = AF_INET;inet_pton(AF_INET,ip,&server_address.sin_addr);server_address.sin_port = htons(port);int sockfd = socket(PF_INET, SOCK_STREAM, 0);assert(sockfd >= 0);int connfd = connect(sockfd, (struct sockaddr *)&server_address,sizeof(server_address)); char buffer_recv[BUF_SIZE] = {0};char buffer_send[BUF_SIZE] = {0};while(1){if(connfd < 0){printf("connection failed\n");}else{memset(buffer_send,0,BUF_SIZE);memset(buffer_recv,0,BUF_SIZE);printf("client:");fgets(buffer_send,BUF_SIZE,stdin);send(sockfd, buffer_send, strlen(buffer_send), 0);if(strcmp(buffer_send,"quit\n") == 0){printf("Communications is over!\n");break;}/*send為quit表示客戶端請求斷開連接,退出循環(huán)*/int ret = recv(sockfd,buffer_recv,BUF_SIZE-1,0);if(strcmp(buffer_recv,"quit\n") == 0){printf("Communications is over!\n");break;}/*recv為quit表示服務(wù)器請求斷開連接,退出循環(huán)*/printf("server:%s",buffer_recv);}} close(connfd);close(sockfd);return 0; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
3、程序測試(Test):
測試中把文件描述輸出了一下,可以觀察到每個進(jìn)程有屬于自己的一套描述符,而且都是從3開始。因為0,1,2已經(jīng)被標(biāo)準(zhǔn)輸入輸出一標(biāo)準(zhǔn)錯誤占用了。
1.Client to quit at first:?
2.Server to quit at first:?
總結(jié)
以上是生活随笔為你收集整理的基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 当贝b3如何才能安装安兔兔评测
- 下一篇: Linux socket编程,对套接字进