转自: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;/*IP地址,struct in_addr{in_addr_t s_addr;}*/char                sin_zero[8];
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(2)、通用套接字地址结构体:

struct sockaddr{uint8_t       sa_len;sa_famliy     sa_famliy;char          sa_data[14];
};
  • 1
  • 2
  • 3
  • 4
  • 5

2、建基本框架所使用的函数,这些函数都是系统调用(man 2 function),失败都会设置一个errno错误标志:

#include<sys/socket.h>
  • 1

(1)、socket:

int socket(int domain,int type, int protocol);
/*
创建一个套接字:
返回值:创建成功返回一个文件描述符(0,1,2已被stdin、stdout、stderr占用,所以从3开始)失败返回-1。
参数:domain为协议家族,TCP属于AF_INET(IPV4);type为协议类型,TCP属于SOCK_STREAM(流式套接字);最后一个参数为具体的协议(IPPOOTO_TCP为TCP协议,前两个已经能确定该参数是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);
/*
将创建的套接字与地址端口等绑定
返回值:成功返回0,失败返回-1.
参数:sockfd为socket函数返回接受的文件描述符,addr为新建的IPV4套接字结构体注意:定义若是使用struct sockaddr_in(IPV4结构体)定义,但是该参数需要将struct sockaddr_in *类型地址强转为struct sockaddr *类型(struct sockaddr是通用类型)。最后一个参数为该结构体所占字节数。
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(3)、listen:

int listen(int sockfd,int backlog);
/*
对创建的套接字进行监听,监听有无客户请求连接
返回值:有客户请求连接时,返回从已完成连接的队列中第一个连接(即完成了TCP三次握手的的所有连接组成的队列),否则处于阻塞状态(blocking)。
参数:
sockfd依然为socket函数返回的文件描述符;
blocklog为设定的监听队列的长度。可设为5、10等值但是不能大于SOMAXCONN(监听队列最大长度)
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

说说监听队列(如下图所示): 

监听队列包括请求连接建立过程中的两个子队列:未完成连接的队列和已完成连接的队列。区分的标志就是:是否完成TCP三次握手的过程。服务器从已完成连接的队列中按照先进先出(FIFO)的原则进行接收。 
(4)、connect和accept:

int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
/*
客户端请求连接
返回值:成功返回0,失败返回-1
参数:客户端的socket文件描述符,客户端的socket结构体地址以及结构体变量长度
*/
int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);
/*
从监听队列中接收已完成连接的第一个连接
返回值:成功返回0,失败返回-1
参数:服务器socket未见描述符,服务器的socket结构体地址以及结构体变量长度
*/
  • 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);
/*
发送数据
返回值:成功返回发送的字符数,失败返回-1
参数:buf为写缓冲区(send_buf),len为发送缓冲区的大小,flags为一个标志,如MSG_OOB表示有紧急带外数据等
*/
ssize_t recv(int sockfd,void *buf, size_t len, int flags);
/*
接收数据
返回值参数与send函数相似
不过send是将buf中的数据向外发送,而recv是将接收到的数据写到buf缓冲区中。
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(6)、close:

int close(int fd);
/*
关闭套接字,类似于fclose,fd为要关闭的套接字文件描述符
失败返回-1,成功返回0
*/
  • 1
  • 2
  • 3
  • 4
  • 5

3、其它函数:

(1)、字节序转换函数:

/*由于我们一般普遍所用的机器(x86)都是小端存储模式或者说叫做小端字节序,而网络传输中采用的是大端字节序,所以要进行网络通讯,就必须将进行字节序的转换,之后才可以进行正常信息传递。*/uint32_t htonl(uint32_t hostlong);/*主机字节序转换成网络字节序*/
uint32_t ntohl(uint32_t netlong);/*网络字节序转换成主机字节序*/
  • 1
  • 2
  • 3
  • 4

(2)、地址转换函数:

/*类似的原因,由于网络传输是二进制比特流传输,所以必须将我们的常用的点分十进制的IP地址,与网络字节序的IP源码(二进制形式)进行互相转换才可以将数据传送到准确的地址*/
int inet_aton(const char * cp,struct in_addr * inp);/*将字符串cp表示的点分十进制转换成网络字节序的二进制形式后存储到inp中*/
char * inet_ntoa(struct in_addr * in);/*将网络字节序的二进制形式转换成点分十进制的字符串形式,返回该字符串的首地址*/
in_addr_t inet_addr(const char * cp);/*与inet_aton的功能相同*/
  • 1
  • 2
  • 3
  • 4

三、代码实现:

(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 1024//缓冲区大小宏定义int main (int argc,char * argv[])/*接收IP地址和端口号*/
{const char * ip = argv[1];int port = atoi(argv[2]);/*将输入的端口号由字符串转换为整数类型*//*结构体定义与初始化*/struct sockaddr_in address;bzero(&address,sizeof(address));/*初始化清零,类似于memset函数*/address.sin_family = AF_INET;inet_pton(AF_INET,ip,&address.sin_addr);/*inet_pton是inet_aton的升级版,随IPV6的出现而出现*/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));/*绑定IP地址、端口号等信息*/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;}/*recv为quit表示客户端请求断开连接,退出循环*/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表示服务器请求断开连接,退出循环*/}}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表示客户端请求断开连接,退出循环*/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表示服务器请求断开连接,退出循环*/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: 

基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序相关推荐

  1. 基于Linux的Socket编程之TCP全双工Server-Client聊天程序

    转载:http://blog.csdn.net/apollon_krj/article/details/53437764#0-tsina-1-58570-397232819ff9a47a7b7e80a ...

  2. Linux下Socket编程之TCP应用

    现在,我们用前面所构建的socket类,重新设计<Linux下Socket编程之TCP Server端>中echo的服务器,然后设计客户端程序. echo服务器的工作原理很简单: 1.接收 ...

  3. Linux C socket 编程之TCP

    本文主要是,简单实现tcp连接的两个程序.本文编写,假设读者有socket 编程思想.熟悉C编程. 服务端: #include <stdio.h> #include <stdlib. ...

  4. Linux下Socket编程之TCP Server端

    一.建模 绝大部分关于socket编程的教程总是从socket的概念开始讲起的.要知道,socket的初衷是个庞大的体系,TCP/IP只是这个庞大体系下一个很小的子集,而我们真正能用上的更是这个子集中 ...

  5. Linux下Socket编程之TCP原理

    一.Socket异常信息 之所以把对异常信息的介绍放到原理之前讲,是因为由于socket本身的复杂性,导致了产生各种异常的复杂性.我们应该时刻铭记的是,sokcet本身属于系统(OS),是系统对TCP ...

  6. Linux下socket编程之UDP简单实现

    本文实现一个简单的UDP小例子,来说明Linux下socket编程之UDP的简单实现.本文主要包括三个部分:服务器端的实现,客服端的实现和通信测试.实现的功能:客服端发送一条消息给服务器端,服务器端把 ...

  7. 基于Linux用C语言实现TCP半双工通信和UDP半双工通信

    文章目录 TCP协议/UDP协议介绍 三种通信方式 实现TCP半双工通信 所用到的结构体与函数 源代码 运行结果 实现UDP半双工通信 源代码 运行结果 参考文章 TCP协议/UDP协议介绍 TCP/ ...

  8. windows Socket编程之TCP服务端与客户端

    在前面的文章中有一篇讲到了命名管道通信,它是创建一根管道来进行进程之间或网络之间通信的.但是它有些缺陷,比如说效率较低等.而从这篇文章开始将介绍socket编程.socket是通过TCP,UDP,IP ...

  9. php soecket服务器搭建_Linux系统编程(32)—— socket编程之TCP服务器与客户端

    TCP协议的客户端/服务器程序的一般流程 aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgQAAAJmCAYAAAAjPpjHAAAgAElEQVR4A ...

最新文章

  1. 无言的鞭策:日本20年19人获自然科学诺奖
  2. WPF ComboBox_SelectionChange事件中获取当前文本的方法
  3. [转载] 七龙珠第一部——第095话 悟空对抗克林
  4. 用计算器计算“异或CRC”
  5. 使用router-view时组件之间的通信
  6. Linux(5) 组管理和权限管理
  7. 互联网产品开发中的“快”字诀
  8. hadoop集群全纪录
  9. 【Spring揭秘】Spring简介
  10. python安卓app下载_【Python教学视频手机下载】Python教学app下载 v1.0 安卓版-趣致软件园...
  11. Shopee面试问题整理
  12. 22071班(8月16日作业)
  13. 数学中 arg min是什么意思
  14. Debian参考手册(3-4)
  15. 通达信标记符号_通达信各种符号
  16. 校招 - 行业测评题、图形推理题、逻辑思维面试题,解题技巧汇总
  17. [Hadoop]Hadoop Archives
  18. 玩转阿里云:从零到一上手玩转云服务器学习报告
  19. 旅游流的概念_旅游流
  20. vue实现点击按钮展开侧边栏,再点击按钮收起

热门文章

  1. AutoMapper的使用
  2. [奇葩 bug]视图在 ipad5 上正常显示,在 iPad3上超出了边界
  3. vector function trmplate
  4. Fix “Windows cannot access the specified device path or file” Error
  5. 【转】三五个人十来条枪 如何走出软件作坊成为开发正规军
  6. ajax 浏览器后退,全站Ajax浏览器后退方法
  7. html属性可以用来定义内联样式,18年6月考试《网页设计与制作》期末大作业.doc...
  8. 标题 计算机构自由度时主要步骤有哪些,2010年1月全国自考混凝土结构设计试题和答案...
  9. 福州java培训哪里好_南通java培训哪家好
  10. mysql 5.7 mirror_Centos7 Docker离线部署Mysql5.7