作者:2010级嵌入式 吴晓培
问题由来: http://blog.chinaunix.net/uid-14735472-id-3216962.html
[root@bogon ~]# gedit server.c
/* server.c */
/* server.c */
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
char buf[MAXLINE];
int client[FD_SETSIZE];
int i;
int maxi;
int doit(char *var, char *vbr);
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd; int p=0,l=0,k=0,j;
int nready, client[FD_SETSIZE];//nready存放有数据请求的客户端的个数;FD_SETSIZE是tcp最大连接数,client[FD_SETSIZE] 存放有数据请求的客户端;
ssize_t n;
pthread_t tidA;
fd_set rset, allset;
char buf[MAXLINE];//开辟一块缓冲区,用于存放客户端与服务器端交互时的数据;
memset(buf,0,MAXLINE);
char str[INET_ADDRSTRLEN];
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr;
char dbuf[]="--";
char cbuf[]="已登录!";
char fbuf[]="收到的信息";
char ebuf[]="用户已登录!请重新打开窗口输入!";
char sookset1[MAXLINE][MAXLINE];
memset(sookset1,0,MAXLINE*MAXLINE*sizeof(char));
char buf2[MAXLINE][MAXLINE];
char buf3[MAXLINE][MAXLINE];
memset(buf3,0,MAXLINE*MAXLINE*sizeof(char));
listenfd = Socket(AF_INET, SOCK_STREAM, 0);//socket()打开一个网络通讯端口,返回一个套接字描述符给listenfd;
bzero(&servaddr, sizeof(servaddr));//首先将结构体清零;
servaddr.sin_family = AF_INET; //设置地址类型为AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//转换ip地址字节序,网络地址为INADDR_ANY,这个宏表示本地任意IP地址
servaddr.sin_port = htons(SERV_PORT);//转换端口的字节序。到目前为止,结构体servaddr的设置已经完毕;
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //将所监听的端口号与服务器的地址、端口绑定;
Listen(listenfd, 20);//listen()声明服务器处于监听状态,并且最多允许有20个客户端处于连接待状态;
maxfd = listenfd;//将所监听的最大的套接字描述符赋给maxfd;
maxi = -1;
for (i = 0; i client[i] = -1;
FD_ZERO(&allset); //将allset套接字描述符集清空。
FD_SET(listenfd, &allset);//向allset套接字描述符集中添加服务器所监听到的端口(即listenfd所接受到的请求);
for (;;) {
rset = allset;// 把allset套接字描述符集的内容赋给rset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);//select用于监听多个阻塞的文件描述符,当这些被阻塞的端口有数据请求或要与服务器交互时,select就会返回请求客户端的个数给nready,若没有,则返回0,若select调用出错,则会返回一个负值。由于最后一个关于时间的参数值是null,所以select会一直阻塞等待着client的请求,直到有数据交互的客户端产生。maxfd+1表示集合中描述符的范围即所有描述符的最大值加1,rset则是在关注哪个客户端有数据可读了,就把客户端的套接字描述符添加在rset集合中。
if (nready < 0)
perr_exit("select error");//select调用出错时,会返回一个负数给nready。该语句判断select是否调用成功。
if (FD_ISSET(listenfd, &rset))
//判断listenfd所接受到的客户端的请求是否在rset集合中,这是一个监听到的客户端与所监听客户端中有数据请求的客户端的一个比对,测试该数据请求的客户端是否在监听的队列中。
{
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);//接受客户端的连接请求,与客户端建立连接。
printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)//for循环,i作为client的下标,表示放有数据请求的客户端的最小索引
if (client[i] < 0) {
printf("i=%d\n",i);
//打印I的值,i从0开始加
client[i] = connfd; /* save descriptor */
sookset1[i][0]=connfd;
//定义二维数组只用下标为[i][0]的位置来存放文件描述符!每打开一个客户端链接i的值自动的加加。。i从0开始加..存放客户端文件描述符的分别是[0][0],[1][0],[2][0]
printf("sookset1[p][0]=%s\n",sookset1[p]);
//控制流程将sookset1[p]的值打印出来。
break;
}
if (i == FD_SETSIZE) {
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd, &allset);
if (connfd > maxfd)
maxfd = connfd;
if (i > maxi)
maxi = i;
if (--nready == 0)//若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;
continue;//继续执行for循环,查找数据请求的客户端
}
for (i = 0; i <= maxi; i++) {
if ( client[i] < 0)//把client[i]中存放的客户端的套接字描述符赋给sockfd。若小于0,表示这个client[i]中没有放置套接字描述符。
{continue;}else
{
sockfd = client[i];
}
lable1: if (FD_ISSET(sockfd, &rset)) {
memset(buf,0,MAXLINE);//初始化buf数组
if ((n = Read(sockfd, buf, MAXLINE)) == 0)
// 读入客户端的数据,若n等于0表示客户端已经关闭了连接;
{
Close(sockfd);//客户端关闭连接了,服务器也关闭与客户端相应的连接;
FD_CLR(sockfd, &allset);
client[i] = -1;//同时将放置这个客户端套接字的数组位置设为-1,用来存放下一次的客户端数据请求的描述符;
} else {
printf("maxi=%d\n",maxi);
for(j = 0; j <= maxi; j++)
{
printf("buf3[1]=%s\n",buf3[j]);
printf("buf=%s\n",buf);
if(strcmp(buf3[j],buf)==0) //比较看buf存放的客户登录信息是否在buf[3]客户登录信息集中存在!
{
for (i = 0; i <= maxi; i++) //如果存在则把ebuf中的信息循环打印给每一个登录的客户!
{
Write(client[i], ebuf, n);
} goto lable1;
}
}
if(strstr(buf,dbuf)==NULL)
//用来判断客户端输入的是用户名还是信息!查找buf中是否含有dbuf,我觉的这个用的不好,但是一时也没想开咋该。
{
printf("执行了一次!\n"); //成功执行这下面的代码,控制流程
printf("i=%d\n",i); //打印活动的文件描述符的下标!从0开始
strcat(sookset1[i],buf);//将文件描述符和用户输入的用户名进行链接,分别放入sookset1中
printf("sookset1[i]=%s\n",sookset1[i]); //打印链接后的sookset1[i]
strcat(buf2[i],buf);
strcat(buf3[i],buf);
printf("buf2[i]=%s\n",buf2[i]);
strcat(buf2[i],dbuf);//必须是英文情况下的--
strcat(buf2[i],cbuf);//执行后即为谁--已登录
//strcat(buf2[i],buf);
for (i = 0; i <= maxi; i++)
for (j = 0; j<= maxi; j++)
{
Write(client[i], buf2[j], n);//每次客户运行将已登录的信息循环发给每个用户
}//将客户端输入的信息循环写入
}else{ //如果输入的是信息执行下列代码
for(j=0;j<=maxi;j++)
{
printf("打印@\n");
printf("buf=%s\n",buf);//将读入的客户端信息放入buf中并打印
printf("sookset1[j]=%s\n",sookset1[j]); //将此时sookset1[j]的信息打印出来
if(doit(sookset1[j],buf)==1)//判断sookset1[j]的信息何buf的信息是否相等
{
printf("sookset1[j][0]=%c\n",sookset1[j][0]); //打印此时的文件描述符
j=(int)sookset1[j][0];//将sookset1[j][0]中的文件描述符转化为整形
printf("%d\n",j);
Write(j, buf, n);//将信息写入客户端
}
}
}
}
if (--nready == 0)
break;
}
}
}
}
int doit(char *var, char *vbr) //函数用来比较sookset1[j]和buf是否相等,此时sookset1[j]相当于一维字符串数组
{
int k;
for(k=0;k<10;k++)
{
if(var[k+1]=='-' || vbr[k]=='-')
//for循环下标从头开始比较,因为sookset1[j]中sookset1[j][0]为文件描述符所以从k+1处开始比较
{
break;
}else{
if(var[k+1]==vbr[k]) //判断var和vbr下标为k+1和k的元素的只是否相同!如果相同则进行下一次比较!
{
continue;
}else{
if(var[k+1]!=vbr[k])//如果var和vbr下标为k+1和k的元素不相同则退处函数返回0
{
return 0;
}
}
}
}
return 1;
}
[root@bogon ~]# gedit client.c
/* client.c */
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
void *doit(void *arg);
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE]; //用来存放
memset(buf,0,MAXLINE); //初始化数组
int sockfd, n;
pthread_t tidA; //定义线程变量
if (argc != 2) {
fprintf(stderr,"请输入要发送的信息\n");
exit(1);
}
strcpy(buf,argv[1]); //将argv[1]里的数据复制到buf中!buf用于存放客户端与服务器端交互时的数据;
sockfd = Socket(AF_INET, SOCK_STREAM, 0); //socket()打开一个网络通讯端口,返回一个套接字描述符给sockfd;
bzero(&servaddr, sizeof(servaddr));//首先将servaddr结构体清零;
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//与服务器建立链接;
printf("请输入用户名形如xxx--xxx\n");
printf("请勿输入重复的用户名!发送信息时请输入要发送的用户名+您要输入的信息!\n");
printf("如果输入重复的用户名!请重新打开窗口!\n");
printf("请输入esc退出程序!\n");
printf("当前登录用户是%s\n",buf);
write(sockfd,buf,MAXLINE); //把buf里的信息写到sockfd文件描述符里
memset(buf,0,MAXLINE);
while(1)
{
pthread_create(&tidA, NULL, &doit,&sockfd); //创建线程函数用来和服务器进行发送信息。
}
sleep(3); //主线程睡3秒
return 0;
}
void *doit(void *arg) //线程函数
{
int conut;
int n;
conut = *((int*)arg); //将socked套接字赋值给conut;
char buf[MAXLINE];
memset(buf,0,MAXLINE);
while(1)
{
memset(buf,0,MAXLINE); //每次读之前初始化数组
n = Read(conut, buf, MAXLINE); //将count的数据读到buf中
if (n == 0) //若n等于0表示服务器已经关闭了连接;
{
printf("the servre has closed.\n");
exit(1); //客户端退出程序
}else
{
printf("%s\n",buf);
printf("------------------------\n");
fgets(buf, MAXLINE, stdin); //用fgets从键盘读数据到buf中;
if(memcmp(buf,"esc",3)==0) //如果客户端输入的为esc则客户端退出程序
{
exit(1);
}
Write(conut, buf, strlen(buf)); //把buf中的数据写到socked文件描述符对应的共享区域!
}
}
}
[root@bogon ~]# gedit wrap.h
#include
#include
#include
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t * salenptr)
{
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (bind(fd, sa, salen) < 0)
perr_exit("bind error");
}
void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (connect(fd, sa, salen) < 0)
perr_exit("connect error");
}
void Listen(int fd, int backlog)
{
if (listen(fd, backlog) < 0)
perr_exit("listen error");
}
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
void Close(int fd)
{
if (close(fd) == -1)
perr_exit("close error");
}
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break;
nleft -= nread;
ptr += nread;
}
return n - nleft;
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100];
if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ((rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n - 1;
} else
return -1;
}
*ptr = 0;
return n;
}
执行过程:
执行过程:
[root@localhost 单播]# ./clie wxp //客户端一
请输入用户名形如xxx--xxx
请勿输入重复的用户名!发送信息时请输入要发送的用户名+您要输入的信息!
如果输入重复的用户名!请重新打开窗口!
请输入esc退出程序!
当前登录用户是wxp
wxp--已登录!
------------------------
wxp--已登录!
------------------------
zls--已登录!
------------------------
zls--老师您好
[root@localhost 单播]# ./clie zls //客户端二
请输入用户名形如xxx--xxx
请勿输入重复的用户名!发送信息时请输入要发送的用户名+您要输入的信息!
如果输入重复的用户名!请重新打开窗口!
请输入esc退出程序!
当前登录用户是zls
wxp--已登录!
------------------------
zls--已登录!
------------------------
zls--老师您好!
------------------------
阅读(1201) | 评论(0) | 转发(0) |