题目:编写一个同步服务器模型
要求:
)客户端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程序练习十九的更多相关文章

  1. Linux学习之第十九、条件判断

    原文地址:http://vbird.dic.ksu.edu.tw/linux_basic/0340bashshell-scripts_4.php 条件判断式 只要讲到『程序』的话,那么条件判断式,亦即 ...

  2. 鸟哥的Linux私房菜——第十九章:例行命令的建立

    视频链接:http://www.bilibili.com/video/av11008859/ 1. 什么是例行性命令 (分为两种,一种是周期性的,一种是突发性的)1.1 Linux 工作排程的种类: ...

  3. Linux 入门记录:十九、Linux 包管理工具 RPM

    一.源代码管理 绝大多数开源软件都是直接以源代码形式发布的,一般会被打包为 tar.gz 的归档压缩文件.程序源代码需要编译为二进制可执行文件后才能够运行使用.源代码的基本编译流程为: ./confi ...

  4. Linux系列教程(十九)——Linux文件系统管理之手工分区

    上篇博客我们首先介绍了硬盘为什么要分区,以及Linux系统的几种分区类型,然后介绍了Linux系统几个常用的文件系统命令,最后讲解了挂载命令,并通过实例演示了如何挂载光盘和U盘. 本篇博客我们将介绍l ...

  5. Linux学习之CentOS(十九)------linux 下压缩与解压之 tar、gzip、bzip2、zip、rar

    将文件存储到归档文件中或者从归档文件中获取原始文件,以及为文件创建归档文件 tar [option] [modifiers] [file-list] 参数 file-list是tar进行归档和提取的文 ...

  6. KALI LINUX WEB 渗透测试视频教程—第十九课-METASPLOIT基础

    原文链接:Kali Linux Web渗透测试视频教程—第十九课-metasploit基础 文/玄魂 目录 Kali Linux Web 渗透测试视频教程—第十九课-metasploit基础..... ...

  7. centos LAMP第一部分-环境搭建 Linux软件删除方式,mysql安装,apache,PHP,apache和php结合,phpinfo页面,ldd命令 第十九节课

    centos LAMP第一部分-环境搭建  Linux软件删除方式,mysql安装,apache,PHP,apache和php结合,phpinfo页面,ldd命令 第十九节课 打命令之后可以输入: e ...

  8. 学习笔记:CentOS7学习之十九:Linux网络管理技术

    目录 学习笔记:CentOS7学习之十九:Linux网络管理技术 本文用于记录学习体会.心得,兼做笔记使用,方便以后复习总结.内容基本完全参考学神教育教材,图片大多取材自学神教育资料,在此非常感谢MK ...

  9. 鸟哥的linux私房菜——第十六章学习(程序管理与 SELinux 初探)

    第十六章.程序管理与 SE Linux 初探 在 Linux 系统当中:"触发任何一个事件时,系统都会将他定义成为一个程序,并且给予这个程序一个 ID ,称为 PID,同时依据启发这个程序的 ...

随机推荐

  1. [翻译]用 Puppet 搭建易管理的服务器基础架构(4)

    我通过伯乐在线翻译了一个Puppet简明教程,一共分为四部分,这是第四部分. 原文地址:http://blog.jobbole.com/89214/ 本文由 伯乐在线 - Wing 翻译,黄利民 校稿 ...

  2. jq倾斜的动画导航菜单

    效果预览网址:http://keleyi.com/keleyi/phtml/jqmenu/index.htm 支持IE.Chrome.火狐等浏览器 完整源代码,保存到HTML文件打开也可查看效果: & ...

  3. navigationController 返回前N个视图

    前提是,由N个视图跳转过来的. //返回前n个 NSInteger index=[[self.navigationController viewControllers]indexOfObject:se ...

  4. Blink, 通向哈里·波特的魔法世界

    <哈里·波特>的故事里面,魔法界的新闻报纸都是动画的,配图带有动画效果.能够回放新闻的主要场景. 初次看到这个,感觉还挺新鲜的.不过现在,Blink 这样的 App 可以让这个魔法世界的幻 ...

  5. SharePoint 2007 Full Text Searching PowerShell and CS file content with SharePoint Search

    1. Ensure your site or shared folder in one Content Source. 2. Add file types. 3. The second step in ...

  6. 机顶盒上gridview+ScrollView的使用。

    最近在机顶盒上做一个gridview, 其焦点需要在item的子控件上,但gridview的焦点默认在item上,通过 android:descendantFocusability="aft ...

  7. Volley框架设置sessionid

    (偷懒,写简略点) 自定义一个Request类 public class MyRequest extends Request<JSONObject>   存储上一次连接的sessionid ...

  8. Android Support Library介绍

    v4 Support Library 这个库是为Android 1.6(API版本为4)及以上的版本设计的,它包含大部分高版本中有而低版本中没有的API,包括application component ...

  9. IOS开发基础知识--碎片14

    1:ZIP文件压缩跟解压,使用ZipArchive 创建/添加一个zip包 ZipArchive* zipFile = [[ZipArchive alloc] init]; //次数得zipfilen ...

  10. AlertDialog的六种创建方式

    AlertDialog的六种创建方式 创建AlertDialog的步骤: 1.创建AlertDialog.Builder对象 2.调用Builder对象的setTitle方法设置标题,setIcon方 ...