文章目录:
一:wrap常用函数封装
wrap.h
wrap.c
server.c封装实现
client.c封装实现
二:多进程process并发服务器
server.c服务器
实现思路
代码逻辑
client.c客户端
三:多线程thread并发服务器
server.c服务器
实现思路
代码逻辑
client.c客户端
read 函数的返回值
read 函数的返回值:
1. > 0 实际读到的字节数
2. = 0 已经读到结尾(对端已经关闭)【 !重 !点 !】
3. -1 应进一步判断errno的值:
errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式 读。 没有数据到达。
errno = EINTR 慢速系统调用被 中断。
errno = “其他情况” 异常。
一:wrap常用函数封装
wrap.h
//声明了一些网络编程中常用的函数
//所有的函数都放在条件编译的代码块中,这样只有在编译时定义了 __WRAP_H_ 这个宏
#ifndef __WRAP_H_
#define __WRAP_H_
//perr_exit:用来处理错误并退出程序
void perr_exit(const char *s);
//Accept:用于接受一个TCP连接请求
//它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及一个指向 socklen_t 类型的指针 salenptr
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
//Bind:用于将一个网络地址(包括IP地址和端口号)绑定到一个文件描述符上
//它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及 socklen_t 类型的 salen
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
//Connect:用于建立与远程主机的TCP连接
//它接受一个文件描述符 fd,一个指向 struct sockaddr 结构的指针 sa,以及 socklen_t 类型的 salen
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
//Listen:用于在服务器端创建一个TCP监听队列
//它接受一个文件描述符 fd 和一个指定的最大连接等待数 backlog
int Listen(int fd, int backlog);
//Socket:用于创建一个新的套接字
//它接受一个地址族 family,一个套接字类型 type,以及一个协议编号 protocol
int Socket(int family, int type, int protocol);
//Read:用于从文件中读取数据
//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 ptr,以及要读取的最大字节数 nbytes
ssize_t Read(int fd, void *ptr, size_t nbytes);
//Write:用于将数据写入文件
//它接受一个文件描述符 fd,一个指向要写入数据的缓冲区的指针 ptr,以及要写入的字节数 nbytes
ssize_t Write(int fd, const void *ptr, size_t nbytes);
//Close:用于关闭文件
//它接受一个文件描述符 fd
int Close(int fd);
//Readn:用来读取指定数量的字节
//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 vptr,以及要读取的字节数 n
ssize_t Readn(int fd, void *vptr, size_t n);
//Writen:用来写入指定数量的字节
//它接受一个文件描述符 fd,一个指向要写入数据的缓冲区的指针 vptr,以及要写入的字节数 n
ssize_t Writen(int fd, const void *vptr, size_t n);
//my_read:用来读取数据到一个字符数组中
//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 ptr
ssize_t my_read(int fd, char *ptr);
//Readline:用来读取一行数据
//它接受一个文件描述符 fd,一个指向要读取数据的缓冲区的指针 vptr,以及要读取的最大字节数 maxlen
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
wrap.c
//这个头文件包含了标准库的函数和变量,包括用于内存分配、输入/输出、错误处理等功能的函数和变量
#include <stdlib.h>
//这个头文件包含了标准输入/输出库的函数和变量,包括用于文件操作、标准输入/输出等功能的函数和变量
#include <stdio.h>
//这个头文件包含了用于Unix和类Unix系统中的函数和变量,包括与进程控制、系统调用等相关的函数和变量
#include <unistd.h>
//这个头文件包含了用于错误处理的函数和变量,包括用于表示错误码的宏和全局变量
#include <errno.h>
//这个头文件包含了用于网络编程的函数和变量,包括用于创建、操作套接字等功能的函数和变量
#include <sys/socket.h>
//perr_exit:用来处理错误并退出程序
void perr_exit(const char *s)
{
perror(s);
exit(-1);
}
//Accept:用于接受一个TCP连接请求
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;
}
//Bind:用于将一个网络地址(包括IP地址和端口号)绑定到一个文件描述符上
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
//Connect:用于建立与远程主机的TCP连接
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
n = connect(fd, sa, salen);
if (n < 0) {
perr_exit("connect error");
}
return n;
}
//Listen:用于在服务器端创建一个TCP监听队列
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
//Socket:用于创建一个新的套接字
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
//Read:用于从文件中读取数据
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;
}
//Write:用于将数据写入文件
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;
}
//Close:用于关闭文件
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
//Readn:用来读取指定数量的字节
/*参三: 应该读取的字节数 读 N 个字节*/ //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n; //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; //nleft = nleft - nread
ptr += nread;
}
return n - nleft;
}
//Writen:用来写入指定数量的字节
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;
}
//my_read:用来读取数据到一个字符数组中
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) { //"hello\n"
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;
}
//Readline:用来读取一行数据
/*readline读一行 --- fgets*/
//传出参数 vptr
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[] = hello\n
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n-1;
} else
return -1;
}
*ptr = 0;
return n;
}
利用封装函数: 联合编译server.c 和 wrap.c 生成 server、 联合编译 client.c 和 wrap.c 生成 client
server.c封装实现
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include "wrap.h"
#define SERV_PORT 6666
int main(void)
{
int sfd, cfd;
int len, i;
char buf[BUFSIZ], clie_IP[BUFSIZ];
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
/*1.socket函数:创建用于建立连接的socket,返回的文件描述符存入link_fd*/
sfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
//设置套接字选项:文件描述符,要设置的选项是套接字的选项,要设置的选项是“重用地址”选项,包含了要设置的选项的值,选项值的长度
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr)); //将指定内存区域的内容初始化为0
serv_addr.sin_family = AF_INET; //IPv4
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //获取本机任意有效IP
serv_addr.sin_port = htons(SERV_PORT); //转为网络字节序的 端口号
/*2.bind函数:绑定服务器端的socket绑定地址结构(IP+port)*/
Bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
/*3.listen函数:设定监听(连接)上线*/
Listen(sfd, 2);
printf("wait for client connect ...\n");
clie_addr_len = sizeof(clie_addr_len);
/*4.accept函数:阻塞等待客户端建立连接*/
cfd = Accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
/*建立连接后打印客户端的IP和端口号 获取客户端地址结构*/
printf("cfd = ----%d\n", cfd);
printf("client IP: %s port:%d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port));
while (1) {
//5. read(fd) 读socket获取客户端数据
len = Read(cfd, buf, sizeof(buf));
Write(STDOUT_FILENO, buf, len);
//6. 小--大写 toupper()
for (i = 0; i < len; i++)
buf[i] = toupper(buf[i]);
//7. write(fd)
Write(cfd, buf, len);
}
//8. close()
Close(sfd);
Close(cfd);
return 0;
}
client.c封装实现
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "wrap.h"
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
int main(void)
{
int sfd, len;
struct sockaddr_in serv_addr;
char buf[BUFSIZ];
//1. socket() 创建socket
sfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr)); //将指定内存区域的内容初始化为0
serv_addr.sin_family = AF_INET; //IPv4
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); //本地字节序(string IP) ---> 网络字节序
serv_addr.sin_port = htons(SERV_PORT); //转为网络字节序的 端口号
//2. connect(); 与服务器建立连接
Connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
while (1) {
fgets(buf, sizeof(buf), stdin);
//3. write() 写数据到 socket
int r = Write(sfd, buf, strlen(buf));
printf("Write r ======== %d\n", r);
//4. read() 读转换后的数据
len = Read(sfd, buf, sizeof(buf));
printf("Read len ========= %d\n", len);
//5. 显示读取结果
Write(STDOUT_FILENO, buf, len);
}
//6. close()
Close(sfd);
return 0;
}
二:多进程process并发服务器
server.c服务器
实现思路
使用多进程并发服务器时要考虑以下几点:
1.父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
2.系统内创建进程个数(与内存大小相关)
3.进程创建过多是否降低整体服务性能(进程调度)
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. while (1) {
cfd = Accpet(); 接收客户端连接请求。
pid = fork();
if (pid == 0){ 子进程 read(cfd) --- 小-》大 --- write(cfd)
close(lfd) 关闭用于建立连接的套接字 lfd
read()
小--大
write()
} else if (pid > 0) {
close(cfd); 关闭用于与客户端通信的套接字 cfd
contiue;
}
}
5. 子进程:
close(lfd)
read()
小--大
write()
父进程:
close(cfd);
注册信号捕捉函数: SIGCHLD
在回调函数中, 完成子进程回收
while (waitpid());
代码逻辑
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
//在回调函数中, 完成子进程回收
void do_sigchild(int num)
{
while (waitpid(0, NULL, WNOHANG) > 0)
;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
pid_t pid;
struct sigaction newact;
//5.父进程
newact.sa_handler = do_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
//注册信号捕捉函数: SIGCHLD
sigaction(SIGCHLD, &newact, NULL);
//1. Socket(); 创建 监听套接字 lfd
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//3. Listen();
Listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
//accept() 阻塞监听客户端连接,接收客户端连接请求
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("-------------------------%d\n", connfd);
pid = fork();
//4.子进程 read(connfd) --- 小-》大 --- write(connfd)
if (pid == 0) {
//关闭用于建立连接的套接字 lfd
Close(listenfd);
while (1) {
//read()
n = Read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
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 < n; i++)
buf[i] = toupper(buf[i]);
//write()
Write(STDOUT_FILENO, buf, n);
Write(connfd, buf, n);
}
Close(connfd);
return 0;
} else if (pid > 0) {
Close(connfd); //关闭用于与客户端通信的套接字 connfd
} else
perr_exit("fork");
}
return 0;
}
client.c客户端
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(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));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
三:多线程thread并发服务器
server.c服务器
实现思路
在使用线程模型开发服务器时需考虑以下问题:
1.调整进程内最大文件描述符上限
2.线程如有共享数据,考虑线程同步
3.服务于客户端线程退出时,退出处理。(退出值,分离态)
4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. while (1) {
cfd = Accept(lfd, );
pthread_create(&tid, NULL, tfn, (void *)cfd);
pthread_detach(tid); // pthead_join(tid, void **); 新线程---专用于回收子线程
}
5. 子线程:
void *tfn(void *arg)
{
// close(lfd) 不能关闭。 主线程要使用lfd
read(cfd)
小--大
write(cfd)
pthread_exit((void *)10);
}
代码逻辑
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
//定义一个结构体, 将地址结构跟cfd捆绑
struct s_info {
struct sockaddr_in cliaddr;
int connfd;
};
//子线程
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看
while (1) {
//读客户端
//read(cfd)
n = Read(ts->connfd, buf, MAXLINE);
//跳出循环,关闭cfd
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break;
}
//打印客户端信息(IP/PORT)
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port));
//小写-->大写
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
//写出至屏幕
Write(STDOUT_FILENO, buf, n);
//回写给客户端
Write(ts->connfd, buf, n);
}
Close(ts->connfd);
return (void *)0;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256]; //创建结构体数组.
int i = 0;
//1. Socket(); 创建 监听套接字 lfd
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfd
bzero(&servaddr, sizeof(servaddr)); //地址结构清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP
servaddr.sin_port = htons(SERV_PORT); //指定端口号
//2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
//3. Listen();
Listen(listenfd, 128); //设置同一时刻链接服务器上限数
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
//accept() 阻塞监听客户端连接
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //子线程分离,防止僵线程产生.
i++;
}
return 0;
}
client.c客户端
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}