Linux Linux程序练习十九
题目:编写一个同步服务器模型
要求:
)客户端A主机给服务器B主机发送报文,
)B服务器主机收到报文以后同时分发给C1主机、C2主机;
)C1主机和C2主机打印出客户端A的报文
bug总结:本来这道题目并不困难,就是向客户端连接池中的其他客户端发送数据,但是我这里出现了一个失误,
我把接收到的数据直接发送了。
第一步:recv_packet(fd, &pack, &buflen, 0);
第二步:send_packet(cltpool[i], &pack, buflen, 0);
但是第二步中buflen的实际长度应该比buflen+4,导致发送的结构体不完整,接收失败,效果类似于没有接收到消息
核心代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include <pthread.h>
#include "commsock.h" #define MAXBUFSIZE 1020 //报文结构
typedef struct _packet
{
int len;
char buf[MAXBUFSIZE];
} Packet; /**
* readn - 读取固定大小的字节
* @fd:文件描述符
* @buf:接收缓冲区
* @count:指定读取字节数
* 成功返回count,失败返回-1,对等方连接关闭返回<count
* */
int readn(int fd, void *buf, int count)
{
int nread = ;
int lread = count;
char *pbuf = (char *) buf;
while (lread > )
{
do
{
nread = read(fd, pbuf, lread);
} while (nread == - && errno == EINTR);
if (nread == -)
return -;
else if (nread == )
return count - lread;
lread -= nread;
pbuf += nread;
}
return count;
} /**
* writen - 写固定大小字节数
* @fd:文件描述符
* @buf:写入缓冲区
* @count:指定写入字节数
* 成功返回count,失败返回-1
* */
int writen(int fd, void *buf, int count)
{
int lwrite = count;
int nwrite = ;
char *pbuf = (char *) buf;
while (lwrite > )
{
do
{
nwrite = write(fd, pbuf, lwrite);
} while (nwrite == - && errno == EINTR);
if (nwrite == -)
return -;
lwrite -= nwrite;
pbuf += nwrite;
}
return count;
} /**
* read_timeout - 读超时检测函数,不含读操作
* @fd:文件描述符
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 成功返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
* */
int read_timeout(int fd, unsigned int wait_seconds)
{
int ret = ;
if (wait_seconds > )
{
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret = select(fd + , &readfds, NULL, NULL, &timeout);
} while (ret == - && errno == EINTR);
//ret==-1
if (ret == )
{
errno = ETIMEDOUT;
ret = -;
} else if (ret == )
{
ret = ;
}
}
return ret;
} /**
* write_timeout - 写超时检测函数,不含写操作
* @fd:文件描述符
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 成功返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
* */
int write_timeout(int fd, unsigned int wait_seconds)
{
int ret = ;
if (wait_seconds > )
{
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret = select(fd + , NULL, &writefds, NULL, &timeout);
} while (ret == - && errno == EINTR);
//ret==-1
if (ret == )
{
errno = ETIMEDOUT;
ret = -;
} else if (ret == )
{
ret = ;
}
}
return ret;
} /**
* activate_nonblock - 设置套接字非阻塞
* @fd:文件描述符
* 成功返回0,失败返回-1
* */
int activate_nonblock(int fd)
{
int ret = ;
int flags = fcntl(fd, F_GETFL);
if (flags == -)
return -;
flags = flags | O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
//ret==-1
return ret;
} /**
* deactivate_nonblock - 设置套接字阻塞
* @fd:文件描述符
* 成功返回0,失败返回-1
* */
int deactivate_nonblock(int fd)
{
int ret = ;
int flags = fcntl(fd, F_GETFL);
if (flags == -)
return -;
flags = flags & (~O_NONBLOCK);
ret = fcntl(fd, F_SETFL, flags);
return ret;
} /**
* connect_timeout - 带超时的connect(函数内已执行connect)
* @fd:文件描述符
* @addr:服务器网络地址结构
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 成功返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
* */
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret = ;
if (wait_seconds > )
{
if (activate_nonblock(fd) == -)
return -;
}
ret = connect(fd, (struct sockaddr *) addr, sizeof(struct sockaddr_in));
if (ret == - && errno == EINPROGRESS)
{
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
int nwrite = select(fd + , NULL, &writefds, NULL, &timeout);
//nwrite==-1 此时ret==-1
if (nwrite == )
errno = ETIMEDOUT;
else if (nwrite == )
{
int err = ;
socklen_t len = sizeof(err);
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
if (ret == )
{
if (err != )
{
errno = err;
ret = -;
}
}
}
}
if (wait_seconds > )
{
if (deactivate_nonblock(fd) == -)
return -;
}
return ret;
} /**
* sock_init - 初始化SOCKET环境
* @connid:连接套接字
* 成功返回0,失败返回错误码
* */
int sock_init(int *connid)
{
int ret = ;
if (connid == NULL)
{
ret = SckParamErr;
printf("cltsock_init() params not correct !\n");
return ret;
}
//init
ret = socket(AF_INET, SOCK_STREAM, );
if (ret == -)
{
ret = SckBaseErr;
perror("socket() err");
return ret;
} else
{
*connid = ret;
ret = ;
}
return ret;
} /**
* connect_server - 连接服务器
* @connid:连接套接字
* @port:端口号
* @ipaddr:IP地址
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 成功返回0,失败返回错误码
* */
int connect_server(int connid, int port, char *ipaddr,
unsigned int wait_seconds)
{
int ret = ;
if (connid < || port < || port > || ipaddr == NULL
|| wait_seconds < )
{
ret = SckParamErr;
printf("cltsock_init() params not correct !\n");
return ret;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = connect_timeout(connid, &addr, wait_seconds);
if (ret == -)
{
if (errno == ETIMEDOUT)
{
ret = SckTimeOut;
printf("connect_timeout() time out !\n");
return ret;
}
ret = SckBaseErr;
perror("connect_timeout() err");
return ret;
}
return ret;
} /**
* send_packet - 发送数据包
* @fd:文件描述符
* @pack:数据包
* @buflen:数据包大小
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 成功返回0,失败返回错误码
* */
int send_packet(int fd, Packet *pack, int buflen, unsigned int wait_seconds)
{
int ret = ;
//可写检测
ret = write_timeout(fd, wait_seconds);
if (ret == -)
{
if (errno == ETIMEDOUT)
{
ret = SckTimeOut;
printf("write_timeout() time out !\n");
return ret;
}
ret = SckBaseErr;
perror("write_timeout() err");
return ret;
}
//发送数据
ret = writen(fd, pack, buflen);
if (ret != buflen)
{
ret = SckBaseErr;
perror("writen() err");
return ret;
} else
{
ret = ;
}
return ret;
} /**
* send_packet - 接收数据包
* @fd:文件描述符
* @pack:数据包
* @buflen:数据包大小
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 成功返回0,失败返回错误码
* */
int recv_packet(int fd, Packet *pack, int *buflen, unsigned int wait_seconds)
{
int ret = ;
//读超时检测
ret = read_timeout(fd, wait_seconds);
if (ret == -)
{
if (errno == ETIMEDOUT)
{
ret = SckTimeOut;
printf("read_timeout() time out !\n");
return ret;
}
ret = SckBaseErr;
perror("read_timeout() err");
return ret;
}
//获取数据长度
int len = ;
ret = readn(fd, &pack->len, );
if (ret == -)
{
ret = SckBaseErr;
perror("readn() err");
return ret;
} else if (ret < )
{
ret = SckPeerClosed;
printf("peer is closed !\n");
return ret;
}
//网络字节序转化成本地字节序
len = ntohl(pack->len);
//获取包体
ret = readn(fd, pack->buf, len);
if (ret == -)
{
ret = SckBaseErr;
perror("readn() err");
return ret;
} else if (ret < len)
{
ret = SckPeerClosed;
printf("peer is closed !\n");
return ret;
} else if (ret == len)
{
ret = ;
}
*buflen = len;
return ret;
} /**
* start_thread - 客户端线程回调函数
* @arg:参数
* */
void *start_thread(void *arg)
{
if (arg == NULL)
{
printf("start_thread() params not correct !\n");
return NULL;
}
int fd = (int) arg;
int ret = ;
//接收信息并且打印
Packet pack;
int buflen = MAXBUFSIZE;
while ()
{
memset(&pack, , sizeof(pack));
ret = recv_packet(fd, &pack, &buflen, );
if (ret != )
{
printf("客户端线程退出了!\n");
//退出当前进程
pthread_exit(NULL);
}
//打印数据
fputs(pack.buf, stdout);
//fflush(stdout);
}
return NULL;
} /**
* run_clt - 运行客户端
* @connid:连接套接字
* @wait_seconds:等待超时秒数,如果为0表示不检测超时
* 失败返回错误码
* */
int run_clt(int connid, unsigned int wait_seconds)
{
int ret = ;
//安装信号
if (signal(SIGPIPE, handler) == SIG_ERR)
{
ret = SckBaseErr;
printf("signal() failed !\n");
return ret;
}
//开始多线程
pthread_t thr1;
pthread_attr_t attr;
pthread_attr_init(&attr);
//设置进程为可分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&thr1, &attr, start_thread, connid) != )
{
ret = SckBaseErr;
printf("pthread_create() failed !\n");
return ret;
}
Packet pack;
memset(&pack, , sizeof(pack));
int buflen = ;
while (fgets(pack.buf, MAXBUFSIZE, stdin) != NULL)
{
//去除\n
buflen = strlen(pack.buf);
pack.len = htonl(buflen);
//发送数据
ret = send_packet(connid, &pack, buflen + , wait_seconds);
if (ret != )
{
return ret;
}
}
return ret;
} /**
* close_socket - 关闭连接
* @fd:文件描述符
* 成功返回0
* */
int close_socket(int fd)
{
int ret = ;
close(fd);
return ret;
} /*
* clear_back - 退格键不回显
* 成功返回0,失败返回错误码
* */
int clear_back()
{
int ret = ;
struct termios term;
memset(&term, , sizeof(term));
//获取当前系统设置
if (tcgetattr(STDIN_FILENO, &term) == -)
{
ret = SckBaseErr;
perror("tcgetattr() err");
return ret;
}
//修改系统设置
term.c_cc[VERASE] = '\b';
//立即生效
if (tcsetattr(STDIN_FILENO, TCSANOW, &term) == -)
{
ret = SckBaseErr;
perror("tcsetattr() err");
return ret;
}
return ret;
} /**
* listen_socket - 创建服务器监听套接字
* @fd:套接字
* @port:端口号
* 成功返回0,失败返回错误码
* */
int listen_socket(int fd, int port)
{
int ret = ;
if (port < || port < || port > )
{
ret = SckParamErr;
printf("listen_socket() params not correct !\n");
return ret;
}
//reuse addr
int optval = ;
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (ret == -)
{
ret = SckBaseErr;
perror("setsockopt() err");
return ret;
}
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr));
if (ret == -)
{
ret = SckBaseErr;
perror("bind() err");
return ret;
}
//listen
ret = listen(fd, SOMAXCONN);
if (ret == -)
{
ret = SckBaseErr;
perror("listen() err");
return ret;
}
return ret;
} /**
* product_clt - 处理客户端信息
* @fd:客户端A文件描述符
* @cltpool:客户端套接字池
* @maxindex:连接池最后一个元素的下标
* 成功返回0,失败返回错误码
* */
int product_clt(int fd, int *cltpool, int maxindex)
{
int ret = ;
//接收客户端信息
Packet pack;
memset(&pack, , sizeof(pack));
int buflen = ;
ret = recv_packet(fd, &pack, &buflen, );
if (ret != )
return ret;
//转发给其他客户端
int i = ;
for (i = ; i <= maxindex; i++)
{
if (cltpool[i] != - && cltpool[i] != fd)
{
//发送信息
ret = send_packet(cltpool[i], &pack, buflen+, );
if (ret != )
return ret;
}
}
return ret;
} /**
* handler - 信号捕捉函数
* @sign:信号值
* */
void handler(int sign)
{
if (sign == SIGPIPE)
{
printf("accept SIGPIPE!\n");
}
} /**
* select_socket - select机制管理客户端连接
* @fd:文件描述符
* 失败返回错误码
* */
int select_socket(int fd)
{
int ret = ;
//安装信号
if (signal(SIGPIPE, handler) == SIG_ERR)
{
ret = SckBaseErr;
printf("signal() failed !\n");
return ret;
}
//定义客户端套接字临时变量
int conn = ;
struct sockaddr_in peeraddr;
socklen_t peerlen = ;
//已经处理的select事件
int nread = ;
//创建客户端连接池
int cltpool[FD_SETSIZE] = { };
//初始化连接池
int i = ;
for (i = ; i < FD_SETSIZE; i++)
{
cltpool[i] = -;
}
//定义数组尾部元素下标
int maxindex = ;
//定义最大的套接字(初始值是监听套接字)
int maxfd = fd;
//定义最新的套接字集合
fd_set allsets;
FD_ZERO(&allsets);
//定义需要监听的套接字集合
fd_set readfds;
FD_ZERO(&readfds);
//将监听套接字加入最新的套接字集合
FD_SET(fd, &allsets);
while ()
{
//将最新的套接字集合赋值给需要监听的套接字集合
readfds = allsets;
do
{
nread = select(maxfd + , &readfds, NULL, NULL, NULL);
} while (nread == - && errno == EINTR); //屏蔽信号
if (nread == -)
{
ret = SckBaseErr;
perror("select() err");
return ret;
}
//1.服务器监听套接字处理
if (FD_ISSET(fd, &readfds))
{
//接收到客户端的连接
memset(&peeraddr, , sizeof(peeraddr));
peerlen = sizeof(peeraddr);
conn = accept(fd, (struct sockaddr *) &peeraddr, &peerlen);
if (conn == -)
{
ret = SckBaseErr;
perror("accept() err");
return ret;
}
//将客户端连接添加到连接池
for (i = ; i < FD_SETSIZE; i++)
{
if (cltpool[i] == -)
{
if (i > maxindex)
{
maxindex = i;
}
cltpool[i] = conn;
break;
}
}
if (i == FD_SETSIZE)
{
ret = SckBaseErr;
close(conn);
printf("客户端连接池已满!\n");
return ret;
}
if (conn > maxfd)
maxfd = conn;
//将该客户端套接字加入到最新套接字集合
FD_SET(conn, &allsets);
printf("server accept from :%s\n", inet_ntoa(peeraddr.sin_addr));
if (--nread <= )
continue;
}
//处理客户端请求
if (nread <= )
continue;
for (i = ; i <= maxindex; i++)
{
if (cltpool[i] == -)
continue;
if (FD_ISSET(cltpool[i], &readfds))
{
//处理客户端请求
ret = product_clt(cltpool[i], cltpool, maxindex);
if (ret != )
{
//从最新的套接字集合中删除
FD_CLR(cltpool[i], &allsets);
//处理请求失败,关闭客户端连接
close(cltpool[i]);
//从客户端连接池中清除
cltpool[i] = -;
break;
}
if (--nread <= )
break;
}
}
}
return ret;
}
Linux Linux程序练习十九的更多相关文章
- Linux学习之第十九、条件判断
原文地址:http://vbird.dic.ksu.edu.tw/linux_basic/0340bashshell-scripts_4.php 条件判断式 只要讲到『程序』的话,那么条件判断式,亦即 ...
- 鸟哥的Linux私房菜——第十九章:例行命令的建立
视频链接:http://www.bilibili.com/video/av11008859/ 1. 什么是例行性命令 (分为两种,一种是周期性的,一种是突发性的)1.1 Linux 工作排程的种类: ...
- Linux 入门记录:十九、Linux 包管理工具 RPM
一.源代码管理 绝大多数开源软件都是直接以源代码形式发布的,一般会被打包为 tar.gz 的归档压缩文件.程序源代码需要编译为二进制可执行文件后才能够运行使用.源代码的基本编译流程为: ./confi ...
- Linux系列教程(十九)——Linux文件系统管理之手工分区
上篇博客我们首先介绍了硬盘为什么要分区,以及Linux系统的几种分区类型,然后介绍了Linux系统几个常用的文件系统命令,最后讲解了挂载命令,并通过实例演示了如何挂载光盘和U盘. 本篇博客我们将介绍l ...
- Linux学习之CentOS(十九)------linux 下压缩与解压之 tar、gzip、bzip2、zip、rar
将文件存储到归档文件中或者从归档文件中获取原始文件,以及为文件创建归档文件 tar [option] [modifiers] [file-list] 参数 file-list是tar进行归档和提取的文 ...
- KALI LINUX WEB 渗透测试视频教程—第十九课-METASPLOIT基础
原文链接:Kali Linux Web渗透测试视频教程—第十九课-metasploit基础 文/玄魂 目录 Kali Linux Web 渗透测试视频教程—第十九课-metasploit基础..... ...
- centos LAMP第一部分-环境搭建 Linux软件删除方式,mysql安装,apache,PHP,apache和php结合,phpinfo页面,ldd命令 第十九节课
centos LAMP第一部分-环境搭建 Linux软件删除方式,mysql安装,apache,PHP,apache和php结合,phpinfo页面,ldd命令 第十九节课 打命令之后可以输入: e ...
- 学习笔记:CentOS7学习之十九:Linux网络管理技术
目录 学习笔记:CentOS7学习之十九:Linux网络管理技术 本文用于记录学习体会.心得,兼做笔记使用,方便以后复习总结.内容基本完全参考学神教育教材,图片大多取材自学神教育资料,在此非常感谢MK ...
- 鸟哥的linux私房菜——第十六章学习(程序管理与 SELinux 初探)
第十六章.程序管理与 SE Linux 初探 在 Linux 系统当中:"触发任何一个事件时,系统都会将他定义成为一个程序,并且给予这个程序一个 ID ,称为 PID,同时依据启发这个程序的 ...
随机推荐
- 使用PowerDesigner设计建造MySQL数据库
使用PowerDesigner设计建造MySQL数据库 一.使用PowerDesigner制作建库脚本 1.设计CDM(Conceptual Data Model) 2.选择 Tools -> ...
- Xdebug文档(四)函数跟踪
Xdebug能让你把所有函数调用,包括参数和返回值以不同的格式记录到文件中. 这些号称“函数跟踪”功能能帮助你面对一个新应用程序,亦或者在程序运行时你想弄清楚它在做什么.函数跟踪功能可以选择性地显示函 ...
- jquery点击复选框触发事件给input赋值
体验效果:http://keleyi.com/keleyi/phtml/jqtexiao/31.htm 代码如下: <!DOCTYPE html> <html xmlns=" ...
- RequireJS 模块的定义与加载
模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染.它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量.RequireJ ...
- html meta标签使用总结
meta标签作用 META标签是HTML标记HEAD区的一个关键标签,提供文档字符集.使用语言.作者等基本信息,以及对关键词和网页等级的设定等,最大的作用是能够做搜索引擎优化(SEO). PS:便于搜 ...
- 获取OpenFileDialog的文件名和文件路径
得到文件名 string fileName = ofd.SafeFileName; 得到路径 string filePath = System.IO.Path.GetDirectoryName(ofd ...
- sublime快捷键
以下是个人总结不完全的快捷键总汇,祝愿各位顺利解放自己的鼠标. 选择类 Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本. Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同 ...
- navigationController 返回前N个视图
前提是,由N个视图跳转过来的. //返回前n个 NSInteger index=[[self.navigationController viewControllers]indexOfObject:se ...
- [转]File Descriptor泄漏导致Crash: Too many open files
在实际的Android开发过程中,我们遇到了一些奇奇怪怪的Crash,通过sigaction再配合libcorkscrew以及一些第三方的Crash Reporter都捕获不到发生Crash的具体信息 ...
- 使用AIDL调用远程服务设置系统时间
在实际工作中,经常遇到客户需要用代码设置系统时间的需求,但是Android非系统应用是无法设置系统时间的.于是,我设计了一个使用系统签名的时间设置服务,客户通过bind调用服务里的方法就能达到设置时间 ...