Linux网络编程中tcp_server和tcp_client函数的封装
本文的主要目的是将server套接字和client套接字的获取,做一个简易的封装,使用C语言完成。
tcp_server
服务器端fd的获取主要分为以下几步:
1.创建socket,这一步仅仅创建一个socket,没有任何特性的属性。
2.绑定网卡和port,一块主机可能有多块网卡,如果我们使用INADDR_ANY,意味着后面接受的TCP连接可以绑定在任意一块网卡上。
例如某台主机的ip地址有两个:192.168.44.136、10.1.1.4,假设绑定的ip采用INADDR_ANY,端口采用9981,那么当接收一个TCP连接时,可能存在192.168.44.136:9981/10.1.1.4:9981/127.0.0.1:9981三种可能性。
如果我们绑定192.168.44.136,那么这个fd接收的连接都是在这个ip上。
有一处特殊的地方,如果主机绑定的是127.0.0.1,那么客户端也必须是本机上的进程。(为什么?)
3.监听端口listen,实际上从这一步开始接收TCP连接。listen函数的第二个参数表示TCP待接受队列的大小,这个队列存储了已经完成三次握手,但还未被accept的连接。如果你设置为3,然后开启4个client,但是server端不进行accept,通过netstat –an | grep 9981,可以查看到只有三条server端的连接处于ESTABLISHED状态,其余的一个处于SYN_RCVD状态。
所以,很多人误以为accept完成三次握手,这是一种错误的想法,三次握手是由底层的协议栈完成的。
然后我们需要使用几个套接字选项,后面会专门写一篇文章总结套接字选项:
1.地址复用SO_REUSEADDR,代码如下:
//地址复用
void set_reuseaddr(int sockfd, int optval)
{
int on = (optval != 0) ? 1 : 0;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt SO_REUSEADDR");
}
2.端口复用SO_REUSEPORT,有的机器不支持,所以需要判断
void set_reuseport(int sockfd, int optval)
{
#ifdef SO_REUSEPORT
int on = (optval != 0) ? 1 : 0;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt SO_REUSEPORT");
#else
fprintf(stderr, "SO_REUSEPORT is not supported.\n");
#endif //SO_REUSEPORT
}
3.禁用Nagle算法,降低局域网的延迟
//设置nagle算法是否可用
void set_tcpnodelay(int sockfd, int optval)
{
int on = (optval != 0) ? 1 : 0;
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == -1)
ERR_EXIT("setsockopt TCP_NODELAY");
}
4.禁用keepAlive机制
void set_keepalive(int sockfd, int optval)
{
int on = (optval != 0) ? 1 : 0;
if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1)
ERR_EXIT("setsockopt SO_KEEPALIVE");
}
我们还需要为我们的tcp_server增加域名解析功能,我们可以绑定”localhost”之类的主机名,而不仅仅是ip地址,所以我们可能使用gethostbyname。
所以tcp_server代码如下:
int tcp_server(const char *host, uint16_t port)
{ //处理SIGPIPE信号
handle_sigpipe(); int listenfd = socket(PF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
ERR_EXIT("socket"); set_reuseaddr(listenfd, 1);
set_reuseport(listenfd, 1);
set_tcpnodelay(listenfd, 0);
set_keepalive(listenfd, 0); SAI addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(port);
if(host == NULL)
{
addr.sin_addr.s_addr = INADDR_ANY;
}
else
{
//int inet_aton(const char *cp, struct in_addr *inp);
if(inet_aton(host, &addr.sin_addr) == 0)
{
//DNS
//struct hostent *gethostbyname(const char *name);
struct hostent *hp = gethostbyname(host);
if(hp == NULL)
ERR_EXIT("gethostbyname");
addr.sin_addr = *(struct in_addr*)hp->h_addr;
}
} if(bind(listenfd, (SA*)&addr, sizeof addr) == -1)
ERR_EXIT("bind"); if(listen(listenfd, SOMAXCONN) == -1)
ERR_EXIT("listen"); return listenfd;
}
tcp_client
客户端也可以绑定port,只不过它不是必须的。代码较简单,如下:
int tcp_client(uint16_t port)
{
int peerfd = socket(PF_INET, SOCK_STREAM, 0);
if(peerfd == -1)
ERR_EXIT("socket"); set_reuseaddr(peerfd, 1);
set_reuseport(peerfd, 1);
set_keepalive(peerfd, 0);
set_tcpnodelay(peerfd, 0); //如果port为0,则不去绑定
if(port == 0)
return peerfd; SAI addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(get_local_ip());
if(bind(peerfd, (SA*)&addr, sizeof(addr)) == -1)
ERR_EXIT("bind client"); return peerfd;
}
上面需要获取本机的ip地址,代码如下:
const char *get_local_ip()
{
static char ip[16]; int sockfd;
if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
{
ERR_EXIT("socket");
} struct ifreq req;
bzero(&req, sizeof(struct ifreq));
strcpy(req.ifr_name, "eth0");
if(ioctl(sockfd, SIOCGIFADDR, &req) == -1)
ERR_EXIT("ioctl"); struct sockaddr_in *host = (struct sockaddr_in*)&req.ifr_addr;
strcpy(ip, inet_ntoa(host->sin_addr));
close(sockfd); return ip;
}
完毕。
Linux网络编程中tcp_server和tcp_client函数的封装的更多相关文章
- linux网络编程中INADDR_ANY的含义
INADDR_ANY选项 网络编程中常用到bind函数,需要绑定IP地址,这时可以设置INADDR_ANY INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或&q ...
- linux网络编程中的shutdown()与close()函数
1.close()函数 int close(int sockfd); //返回成功为0,出错为-1 close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字不能再由cl ...
- Linux 网络编程中的read和write函数正确的使用方式
字节流套接字上的read和write函数所表现的行为不同于通常的文件IO,字节流套接字上调用read和write输入或输出的可能比请求的数量少,然而这不是出错的状态,例如某个中端使read和write ...
- linux网络编程中的超时设置
1 下面是在网上找到的资料,先非常的感谢. 用setsockopt()来控制recv()与send()的超时 在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,而设置收发超时 ...
- linux网络编程中的基本概念
int close(int fd)(假设是服务器端) close 关闭了自身数据传输的两个方向.close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程.该套接字描述符不能 ...
- linux网络编程中阻塞和非阻塞socket的区别
读操作 对于阻塞的socket,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返 回.当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数.当 ...
- linux网络编程中需要注意的信号SIGPIPE
在调试cs时,s端循环收,c端循环发,s端意外崩溃后,c端自动退出,终端提示SIGPIPE导致c端退出.man 7 signal: SIGPIPE Term Broken pipe: write to ...
- Linux网络编程入门 (转载)
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- [转] - Linux网络编程 -- 网络知识介绍
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
随机推荐
- PHP高性能开发-多进程开发
硬件多核时代的软件业以前计算能力的提升一直在摩尔定律的指引下,沿着提升CPU时钟频率这条道路前进,从初期的几十MHz到如今的几GHz.但是,进入2002年以 来,CPU提升主频的困难越来越大,因为主频 ...
- 【SSH原理】ssh用法及命令
什么是SSH? Secure Shell 安全外壳协议 简单说,SSH是一种网络协议,用于 计算机之间的加密登录.如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登 ...
- 放棋游戏(NOIP模拟赛)(DP)
没有原题... 囧.. [问题描述] 游戏规则是这样,有n(1<=n<=100)行格子,第一行由n个格子,第二行有n-1个格子,第三行由n-2个格子,……以此类推,第n行有1个格子.要求再 ...
- (未解决)WIN8下使用POWERSHELL安装python easy_install无法成功
按照https://pypi.python.org/pypi/setuptools#windows-8-powershell介绍的方法, 安装未成功.安装似乎没有启动, 也未安装成功. Windows ...
- sqlite3数据库 sqlite3_get_table
上一篇介绍的sqlite3_exec 是使用回调来执行对select结果的操作.还有一个方法可以直接查询而不需要回调.但是,我个人感觉还是回调好,因为代码可以更加整齐,只不过用回调很麻烦,你得声明一个 ...
- mydumper备份
1.安装工作 cd /usr/local/softwareyum install -y glib2-devel zlib-devel pcre-develwget https://launchpad ...
- [BZOJ3211]花神游历各国&&[BZOJ3038] 上帝造题的七分钟2 树状数组+并查集
3211: 花神游历各国 Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 4057 Solved: 1480[Submit][Status][Discu ...
- Mediocre String Problem (2018南京M,回文+LCP 3×3=9种做法 %%%千年好题 感谢"Grunt"大佬的细心讲解)
layout: post title: Mediocre String Problem (2018南京M,回文+LCP 3×3=9种做法 %%%千年好题 感谢"Grunt"大佬的细 ...
- Codeforces 1037F. Maximum Reduction
总感觉我这种做法会T,一直没写,看了其他人的题解也是这样,,,就果断写了,,可能数据不太深,或者玄学复杂度 题意即求xk-1长度的所有区间的最大值的和,对每一个i(数组下边),他对答案的贡献数量就是在 ...
- POJ 3680 Intervals(费用流+负权优化)
[题目链接] http://poj.org/problem?id=3680 [题目大意] 有N个带权重的区间,现在要从中选取一些区间, 要求任意点都不被超过K个区间所覆盖,请最大化总的区间权重. [题 ...