转自: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: