09嵌入式 应用一斑 王延龙
-------------------------------------------------------------------------------------*/
/* server.c */
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE]; //nready存放有数据请求的连接个数,FD_SETSIZE是tcp最大连接数, client[FD_SETSIZE]存放有数据请求的客户端;
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr;
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); //转换端口的字节序。
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //服务器需要调用bind绑定一个固定的网络地址和端口号;
Listen(listenfd, 20); //listen()声明服务器处于监听状态,并且允许最多有20个客户端处于连接待状态;
maxfd = listenfd; /* initialize */ //将所监听的最大的套接字描述符赋给maxfd;
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++) //FD_SETSIZE的最大值为1024
client[i] = -1; /* -1 indicates available entry */ //将client[i]的值设为-1,client[i]在下文中用来保存最小的套接字描述符,这样可以把建立数据请求的端口赋给前面最小的client[i];
FD_ZERO(&allset); //将allset套接字描述符集清空;
FD_SET(listenfd, &allset); //把服务器所监听到的所有端口添加到allset套接字描述符集中;
for ( ; ; ) { //for 用于循环接受有数据请求,要与服务器交互的client的端口(用select这个系统调用);
rset = allset; /* structure assignment */ // 把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) //select调用出错时,会返回 -1给nready。该语句判断select是否调用成功;
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* new client connection */ //判断listenfd所接受到的客户端的请求是否在rset集合中,这是一个监听到的客户端与所监听的有数据请求的队列客户端中的一个比对,测试该数据请求的客户端是否在监听的队列中;
cliaddr_len = sizeof(cliaddr); //把cliaddr结构体的长度赋给cliaddr_len,作为缓冲区的长度;
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)); //打印客户端的ip地址和端口号;
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break; //把有数据请求的客户端的套接字描述符放置到前面最小的client[i]中;
}
if (i == FD_SETSIZE) { //若i以达到最大值FD_SETSIZE,则表示有数据请求的客户端已达到FD_SETSIZE的最大值;
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd, &allset); /* add new descriptor to set */ //与服务器建立连接并有数据请求的客户端端口添加到allset套接字描述符集中;
if (connfd > maxfd)
maxfd = connfd; /* for select */ //若此时建立并有数据请求的客户端已大于原来的套接字描述符最大值则用connfd从置maxfd;
if (i > maxi) //把maxi重置为当前最大的已建立并有数据的客户端描述符的索引,作为以下客户端请求个数的上限;
maxi = i; /* max index in client[] array */
if (--nready == 0) //若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0) //把client[i]中的套接字描述符赋给sockfd。若小于0,表示这个client[i]中没有套接字描述符;
continue; //继续执行for循环;
if (FD_ISSET(sockfd, &rset)) { //检测sockfd是否在rset这个描述符集中;
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { // 若没有数据读入客户端,则客户端已经关闭了连接;
/* connection closed by client */
Close(sockfd); //客户端关闭连接了,服务器也关闭与客户端相应的连接;
FD_CLR(sockfd, &allset); //在allset集中清空与客户端连接的套接字描述符;
client[i] = -1; //同时将放置这个客户端套接字的数组位置设为-1,用来存放下一次的客户端数据请求的描述符;
} else { //若n不为0,则处理客户端的数据请求;
for (i = 0; i <= maxi; i++) /* check all clients for data */
Write(client[i], buf, n); /* 把 buf写回与服务器相连的每一个的客户*/
}
if (--nready == 0) //再次判断nready,是否进行下一轮的select循环;
break; /* no more readable descriptors */
}
}
}
}
/*------------------------------------------------------------------------------------
执行结果如下:
[root@localhost Desktop]# ./ser
received from 127.0.0.1 at PORT 41438
received from 127.0.0.1 at PORT 41439
-------------------------------------------------------------------------------------*/
------------------------------------ client.c ------------------------------------
/*-------------------------------------------------------------------------------------------
注释:客户端连接服务器的程序,向服务器发信息。
下面的指令是生成可执行文件:
[root@localhost pop3]# gcc client.c -0 client
--------------------------------------------------------------------------------------------*/
/* client.c */
#include /* 包含标准函数库中的输入输出函数的头文件 */
#include /* 包含系统调用函数的头文件 */
#include /* 包含字符数组的函数定义的头文件 */
#include /* IPv4和IPv6的地址格式定义在netinet/in.h中 */
#include "wrap.h" /* 系统函数加上错误处理代码包装成新的函数的头文件 */
#define MAXLINE 80 /* MAXLINE为80*/
#define SERV_PORT 8000 /* 宏定义一个变量SERV_PORT为8000*/
int main(int argc, char *argv[]) /*主函数 */
{
struct sockaddr_in servaddr; /* 定义struct sockaddr_in类型 servaddr变量 */
char buf[MAXLINE]; /*定义一个字符数组 buf[MAXLINE]*/
int sockfd, n; /* 定义整形变量i, sockfd */
sockfd = Socket(AF_INET, SOCK_STREAM, 0); /* 调用Socket函数还回套节子描述符赋值给sockfd*/
bzero(&servaddr, sizeof(servaddr)); /* 对servaddr全部清0 */
servaddr.sin_family = AF_INET; /* 设置地址类型为AF_INE*/
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); /* 由服务器端IP 转化为网络字节序保存在servaddr.sin_addr*/
servaddr.sin_port = htons(SERV_PORT); /* 由主机端口号 转化为网络字节序赋值给servaddr.sin_port */
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));/*户端需要调用connect()连接服务器*/
while(1) /*死循环*/
{ pid_t pid; /*定义pid_t类型的pid*/
char *message; /*定义字符指针类型的message变量*/
int n; /*定义一个整形变量n*/
pid = fork(); /*调用fork()生成一个子进程,返回值可能是父进程, 也可能子进程*/
if (pid < 0) { /*返回发生错误*/
perror("fork failed"); /*输出错误信息*/
exit(1); /*退出*/
}
if (pid == 0) { /*pid == 0是子进程*/
n = Read(sockfd, buf, MAXLINE); /*从服务器sockfd读信息到buf*/
if (n == 0) { /*关闭了连接*/
printf("the other side has been closed.\n"); /*输出错误信息*/
break;
}
else /*n>0,正确读取了信息到buf里*/
Write(STDOUT_FILENO, buf, n); /*把里的信息写到屏幕上*/
} else /*pid >0是父进程*/
{fgets(buf, MAXLINE, stdin); /*调用fgets从键盘输入保存到buf里*/
Write(sockfd, buf, strlen(buf)); /*把buf里的信息写到sockfd对应的网络上*/
}
}
Close(sockfd); /*关闭连接*/
return 0; /*退出并返回0*/
}
/*------------------------------------------------------------------------------------
执行过程如下
客户端.1
[root@localhost ~]# ./cli
ssdfcd /* 输入的信息 */
ssdfcd
sdddddddd /* 输入的信息 */
sdddddddd
客户端.2
[root@localhost Desktop]# ./cli
ssdfcd
sdddddddd
--------------------------------------------------------------------------------------------*/
------------------------------------wrap.h ------------------------------------
/*-------------------------------------------------------------------------------------------
注释:service.c和 client.c都包含了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;
}
/*-------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------*/
注:修改之后解决了服务端关闭之后客户端陷入死循环导致电脑死机的情况,在client.c里添加了break语句,当服务端关闭时,客户端跳出死循环,等待用户下一步操作。
阅读(796) | 评论(0) | 转发(0) |