轉自:http://blog.csdn.net/apollon_krj/article/details/53398448#0-tsina-1-64987-397232819ff9a47a7b7e80a40613cfe1
所謂半雙工通信,即通信雙方都可以實現接發數據,但是有一個限制:只能一方發一方收,之后交換收發對象。也就是所謂的阻塞式的通訊方式。
一、基本框架:
1、首先搞清我們進行編程所處的的位置:

TCP編程,具有可靠傳輸的特性,而實現可靠傳輸的功能并非我們將要做的事(這些事),我們要做的就是在內核實現的基礎上調用系統的API接口直接使用。所以我們所處的位置就是位于應用層面與系統層面之間的。我覺得弄清這點是實現整個通信程序的重中之重。

2、弄清楚此次的目的:實現偽半雙工的通信
為什么說是“偽”半雙工通信,因為真正的半雙工是通信雙方都可以隨時接發數據(只是限制不能同時發,也不能同時收,在同一時刻只能由一方發送,一方接收),而我們要實現的是“傻瓜式”的你一句我一句,因為不是全雙工,而類似于半雙工,我也不知道有沒有更準確的說法,就暫且叫它“偽半雙工吧”!
3、TCP編程框架:
下面這張圖是很多博客中都使用到的一張流圖,其原圖都來自于UNIX網絡編程卷1:套接字聯網API 【史蒂文斯 (W.Richard Stevens)、芬納 (Bill Fenner) 、 魯道夫 (Andrew M.Rudoff)著】這本書。核心思想都是一樣的,所以就直接貼上了:?

二、所用到的結構體與函數:
1、幾個結構體:
(1)、IPV4套接字地址結構體:
struct sockaddr_in{uint8_t sin_len;sa_famliy_t sin_fanliy;in_port_t sin_port;struct in_addr sin_addr;char sin_zero[8];
};
(2)、通用套接字地址結構體:
struct sockaddr{uint8_t sa_lensa_famliy sa_famliychar sa_data[14]
}
2、建基本框架所使用的函數,這些函數都是系統調用(man 2 function),失敗都會設置一個errno錯誤標志:
#include<sys/socket.h>
(1)、socket:
int socket(int domain,int type, int protocol);
(2)、bind:
int bind(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
(3)、listen:
int listen(int sockfd,int backlog);
說說監聽隊列(如下圖所示):?

監聽隊列包括請求連接建立過程中的兩個子隊列:未完成連接的隊列和已完成連接的隊列。區分的標志就是:是否完成TCP三次握手的過程。服務器從已完成連接的隊列中按照先進先出(FIFO)的原則進行接收。?
(4)、connect和accept:
int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);
(5)、send和recv:
ssize_t send(int sockfd,const void * buf,size_t len,int flags);
ssize_t recv(int sockfd,void *buf, size_t len, int flags);
(6)、close:
int close(int fd);
3、其它函數:
(1)、字節序轉換函數:
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
(2)、地址轉換函數:
int inet_aton(const char * cp,struct in_addr * inp);
char * inet_ntoa(struct in_addr * in);
in_addr_t inet_addr(const char * cp);
三、代碼實現:
(1)、服務器(Server):服務器由于不知道客戶何時回請求建立連接,所以必須綁定端口之后進行監聽(Socket、Bind、Listen)?
(2)、客戶端(Client):客戶端只需要向服務器發起請求連接(connect),而不需要綁定與監聽的步驟;?
(3)、請求連接由客戶端發起(主動打開),服務器接受連接請求(被動打開),會經過TCP三次握手過程;而斷開連接服務器和客戶端都可以自行斷開,會經過TCP四次揮手的過程。
1、服務器代碼(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 1024int main (int argc,char * argv[])
{const char * ip = argv[1];int port = atoi(argv[2]);struct sockaddr_in address;bzero(&address,sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET,ip,&address.sin_addr);address.sin_port = htons(port);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock >= 0);int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));assert(ret != -1);ret = listen(sock,5);assert(ret != -1);struct sockaddr_in client;socklen_t client_addrlength = sizeof(client);int connfd = accept(sock,(struct sockaddr *)&client,&client_addrlength);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);ret = recv(connfd, buffer_recv, BUF_SIZE-1, 0);if(strcmp(buffer_recv,"quit\n") == 0){printf("Communications is over!\n");break;}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;}}}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;}int ret = recv(sockfd,buffer_recv,BUF_SIZE-1,0);if(strcmp(buffer_recv,"quit\n") == 0){printf("Communications is over!\n");break;}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):
測試中把文件描述輸出了一下,可以觀察到每個進程有屬于自己的一套描述符,而且都是從3開始。因為0,1,2已經被標準輸入輸出一標準錯誤占用了。
1.Client to quit at first:?

2.Server to quit at first:?
