看过之后别忘了回复评论啊!
前言:局域网中启动一个服务器,客户端要访问服务器;通过服务器让每一个与服务器相连的客户端都能知道任何一个相连的客户发送的信息;下面是实现过程。
这里是在本机上模拟的,打开一个终端窗口作为服务器,再打开终端窗口作为客户端。
------------------------------------ service.c ------------------------------------
/*------------------------------------------------------------------------------------
注释:这里服务器程序server.c,接受客户端的连接,把客户端发送的信息转发送给每一个连接的客户端。
下面的指令是生成可执行文件:
[root@localhost ~]#gcc service.c -o service
-------------------------------------------------------------------------------------*/
/* server.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) /*主函数 */
{
int i, maxi, maxfd, listenfd, connfd, sockfd; /* 定义整形变量i, maxi, maxfd, listenfd, connfd, sockfd */
int nready, client[FD_SETSIZE]; /* 定义整形变量nready和一个整形数组client[FD_SETSIZE]*/
ssize_t n; /* 定义一个ssize_t类型的n*/
fd_set rset, allset; /* 定义 fd_set类型的rset, allset*/
char buf[MAXLINE]; /*定义一个字符数组 buf[MAXLINE]*/
char str[INET_ADDRSTRLEN]; /* 定义一个字符数组str[INET_ADDRSTRLEN]*/
socklen_t cliaddr_len; /* 定义一个socklen_t类型的cliaddr_len*/
struct sockaddr_in cliaddr, servaddr; /* 定义struct sockaddr_in类型cliaddr, servaddr两个变量 */
listenfd = Socket(AF_INET, SOCK_STREAM, 0); /* 调用Socket函数还回套节子描述符赋值给listenfd */
bzero(&servaddr, sizeof(servaddr)); /* 对servaddr全部清0 */
servaddr.sin_family = AF_INET; /* 设置地址类型为AF_INE*/
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 由主机IP 转化为网络字节序赋值给servaddr.sin_addr.s_addr */
servaddr.sin_port = htons(SERV_PORT); /* 由主机端口号 转化为网络字节序赋值给servaddr.sin_port */
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));/* 调用bind绑定一个固定的网络地址和端口号也就是将参数sockfd和servaddr绑定在一起,
sizeof(servaddr)指定结构体的长度*/
Listen(listenfd, 20); /* listen()声明listenfd处于监听状态,并且最多允许有20个客户端处于连接待状态 */
maxfd = listenfd; /* 把listenfd赋值给maxfd因为此时只有 listenfd它是最大的*/
maxi = -1; /* 给maxi赋值为-1 */
for (i = 0; i < FD_SETSIZE; i++) /* for循环FD_SETSIZE=1024次*/
client[i] = -1; /* -1 表示可以利用的*/
FD_ZERO(&allset); /* 把描述符集清0*/
FD_SET(listenfd, &allset); /* 把listenfd放到描述符集allset里*/
for ( ; ; ) { /* for 语句的死循环 */
rset = allset; /* 把 描述符集allset里的描述符赋值给rset*/
nready = select(maxfd+1, &rset, NULL, NULL, NULL); /* 调用select还回rset有效的文件描述符的个数给nready*/
if (nready < 0) /* if条件语句 条件nready < 0*/
perr_exit("select error"); /*调用 perr_exit输出错误信息并退出*/
if (FD_ISSET(listenfd, &rset)) { /* if条件语句 条件调用FD_ISSET函数确认listenfd在 rset*/
cliaddr_len = sizeof(cliaddr); /* 把 结构体cliaddr的长度赋值给cliaddr_len */
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);/* 调用accept()接受连接,accept()返回时传出传出新的套节子描述符(客户端的地址和端口号)给connfd*/
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); /*输出 received from IP at PORT 端口号*/
for (i = 0; i < FD_SETSIZE; i++) /* for 循环从0到1023*/
if (client[i] < 0) { /* if条件语句 条件client[i] < 0表示client[i]是可以利用的*/
client[i] = connfd; /*把 connfd保存在client[i]中*/
break; /* 跳出这个上面最近for的整个循环*/
}
if (i == FD_SETSIZE) { /* if条件语句 条件i == FD_SETSIZE表示已经出错*/
fputs("too many clients\n", stderr); /* 输出错误信息到 stderr*/
exit(1); /* 退出*/
}
FD_SET(connfd, &allset); /* 把connfd放到描述符集allset里 */
if (connfd > maxfd) /* if条件语句 条件connfd > maxfd */
maxfd = connfd; /* 把connfd赋值给 maxfd 保证已有套节子描述符中 maxfd 是最大的,在select 中要用到maxfd*/
if (i > maxi) /* if条件语句 条件i > maxi */
maxi = i; /*maxi 表示已用的 client[i]的最大索引*/
if (--nready == 0) /* if条件语句 条件nready自减1等于0 */
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* for 检查所有的 0到maxi的client[i]里的保存的套节子描述符*/
if ( (sockfd = client[i]) < 0) /* if条件语句 条件client[i]) < 0表示没保存来自client 套节子描述符*/
continue; /* 继续检测*/
if (FD_ISSET(sockfd, &rset)) { /* if条件语句 确认sockfd是不是在rset里 是就执行下面的语句 */
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { /* if条件语句 条件表示客户端关掉了连接 */
Close(sockfd); /* 关掉连接*/
FD_CLR(sockfd, &allset); /*调用 FD_CLR把此sockfd从allset清除*/
client[i] = -1; /* 使保存此sockfd的client[i]有效空闲*/
} else { /* if条件语句 else条件表示读到字节数n>0 */
for (i = 0; i <= maxi; i++) /* check all clients for data */
Write(client[i], buf, n); /* 把 buf写回与服务器相连的每一个的客户*/
}
if (--nready == 0) /* if条件语句 条件nready自减1等于0 */
break; /* no more readable descriptors */
}
}
}
}
/*------------------------------------------------------------------------------------
执行结果如下:
[root@localhost ~]# ./service
received from 127.0.0.1 at PORT 39121
received from 127.0.0.1 at PORT 39122
received from 127.0.0.1 at PORT 39123
-------------------------------------------------------------------------------------*/
------------------------------------ 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"); /*输出错误信息*/
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 ~]# ./client
hfskjhfkshfjs /* 输入的信息 */
hfskjhfkshfjs
sfkjaksfjsjkhf /* 输入的信息 */
sfkjaksfjsjkhf
客户端.2
[root@localhost ~]# ./client
hfskjhfkshfjs
sfkjaksfjsjkhf
客户端.3
[root@localhost ~]# ./client
hfskjhfkshfjs
sfkjaksfjsjkhf
--------------------------------------------------------------------------------------------*/
------------------------------------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;
}
/*-------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------*/
阅读(4869) | 评论(1) | 转发(0) |