基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序
转自: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聊天程序相关推荐
- 基于Linux的Socket编程之TCP全双工Server-Client聊天程序
转载:http://blog.csdn.net/apollon_krj/article/details/53437764#0-tsina-1-58570-397232819ff9a47a7b7e80a ...
- Linux下Socket编程之TCP应用
现在,我们用前面所构建的socket类,重新设计<Linux下Socket编程之TCP Server端>中echo的服务器,然后设计客户端程序. echo服务器的工作原理很简单: 1.接收 ...
- Linux C socket 编程之TCP
本文主要是,简单实现tcp连接的两个程序.本文编写,假设读者有socket 编程思想.熟悉C编程. 服务端: #include <stdio.h> #include <stdlib. ...
- Linux下Socket编程之TCP Server端
一.建模 绝大部分关于socket编程的教程总是从socket的概念开始讲起的.要知道,socket的初衷是个庞大的体系,TCP/IP只是这个庞大体系下一个很小的子集,而我们真正能用上的更是这个子集中 ...
- Linux下Socket编程之TCP原理
一.Socket异常信息 之所以把对异常信息的介绍放到原理之前讲,是因为由于socket本身的复杂性,导致了产生各种异常的复杂性.我们应该时刻铭记的是,sokcet本身属于系统(OS),是系统对TCP ...
- Linux下socket编程之UDP简单实现
本文实现一个简单的UDP小例子,来说明Linux下socket编程之UDP的简单实现.本文主要包括三个部分:服务器端的实现,客服端的实现和通信测试.实现的功能:客服端发送一条消息给服务器端,服务器端把 ...
- 基于Linux用C语言实现TCP半双工通信和UDP半双工通信
文章目录 TCP协议/UDP协议介绍 三种通信方式 实现TCP半双工通信 所用到的结构体与函数 源代码 运行结果 实现UDP半双工通信 源代码 运行结果 参考文章 TCP协议/UDP协议介绍 TCP/ ...
- windows Socket编程之TCP服务端与客户端
在前面的文章中有一篇讲到了命名管道通信,它是创建一根管道来进行进程之间或网络之间通信的.但是它有些缺陷,比如说效率较低等.而从这篇文章开始将介绍socket编程.socket是通过TCP,UDP,IP ...
- php soecket服务器搭建_Linux系统编程(32)—— socket编程之TCP服务器与客户端
TCP协议的客户端/服务器程序的一般流程 aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgQAAAJmCAYAAAAjPpjHAAAgAElEQVR4A ...
最新文章
- 无言的鞭策:日本20年19人获自然科学诺奖
- WPF ComboBox_SelectionChange事件中获取当前文本的方法
- [转载] 七龙珠第一部——第095话 悟空对抗克林
- 用计算器计算“异或CRC”
- 使用router-view时组件之间的通信
- Linux(5) 组管理和权限管理
- 互联网产品开发中的“快”字诀
- hadoop集群全纪录
- 【Spring揭秘】Spring简介
- python安卓app下载_【Python教学视频手机下载】Python教学app下载 v1.0 安卓版-趣致软件园...
- Shopee面试问题整理
- 22071班(8月16日作业)
- 数学中 arg min是什么意思
- Debian参考手册(3-4)
- 通达信标记符号_通达信各种符号
- 校招 - 行业测评题、图形推理题、逻辑思维面试题,解题技巧汇总
- [Hadoop]Hadoop Archives
- 玩转阿里云:从零到一上手玩转云服务器学习报告
- 旅游流的概念_旅游流
- vue实现点击按钮展开侧边栏,再点击按钮收起
热门文章
- AutoMapper的使用
- [奇葩 bug]视图在 ipad5 上正常显示,在 iPad3上超出了边界
- vector function trmplate
- Fix “Windows cannot access the specified device path or file” Error
- 【转】三五个人十来条枪 如何走出软件作坊成为开发正规军
- ajax 浏览器后退,全站Ajax浏览器后退方法
- html属性可以用来定义内联样式,18年6月考试《网页设计与制作》期末大作业.doc...
- 标题 计算机构自由度时主要步骤有哪些,2010年1月全国自考混凝土结构设计试题和答案...
- 福州java培训哪里好_南通java培训哪家好
- mysql 5.7 mirror_Centos7 Docker离线部署Mysql5.7