服务端流程步骤
socket函数创建监听套接字lfd
bind函数将监听套接字绑定ip和端口
listen函数设置服务器为被动监听状态,同时创建一条未完成连接队列(没走完tcp三次握手流程的连接),和一条已完成连接队列(已完成tcp三次握手的连接)
accept函数循环的从已完成连接队列中提取连接,并返回一个新的套接字cfd跟客户端进行通信
fork函数创建一个子进程,让子进程与客户端进行通信
子进程:read函数循环的从r缓冲区读取客户端发送的数据,write函数将要发送的数据写入w缓冲区
close函数关闭套接字
客户端流程步骤
socket函数创建套接字
connect函数连接服务器
write函数将要发送的数据写入w缓冲区,read函数从r缓冲区读取服务器发送给客户端的数据
close函数关闭套接字
客户端跟服务器通信流程图
相关函数
int socket(int domain, int type, int protocol);
功能:创建一个用于网络通信的套接字文件描述符
参数:domain:协议族(AF_INET:ipv4,AF_INET6:ipv6,等等)
type:套接字类型(SOCK_DGRAM:udp,SOCK_STREAM:tcp,等等)
protocol:用于制定某个协议的特定类型,即type类型中的某个类型,通常不用管它,设置 为 0
返回值:成功则返回socket套接字描述符, 失败返回-1,并设置errno
头文件:#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
功能:将sockfd绑定ip和端口
参数:sockfd:套接字
my_addr:存放有协议,ip,端口的结构体信息
addrlen:my_addr结构体大小
返回值:成功返回0,失败返回-1,并设置errno
头文件:#include <sys/types.h>
#include <sys/socket.h>
int listen(int s, int backlog);
功能:让服务器处于被动监听状态,同时创建了一条未完成三次握手的连接队列和一条已经完成三次握 手的连接队列
参数:s:套接字
backlog:支持未完成连接和已完成连接之和的最大值,一般设置128
返回值:成功返回0,失败返回-1,并设置errno
头文件:#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
功能:从已完成连接队列中提取客户端连接
参数:s:套接字
addr:存放成功连接的客户端的ip,端口等信息结构体
addrlen:存放addr结构体大小的变量地址
返回值:成功则返回一个非负整数标识这个连接套接字,是否返回-1
头文件:#include <sys/types.h>
#include <sys/socket.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中
参数:fd:文件描述符
buf:缓冲区
count:读count字节
返回值:成功时返回读取到的字节数,失败返回-1,并设置errno
头文件:#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向文件描述符fd所引用的文件中写入从buf开始的缓冲区中count字节的数据
参数:fd:文件描述符
buf:缓冲区
count:写count字节
返回值:功时返回所写入的字节数,失败返回-1,并设置errno
头文件:#include <unistd.h>
int close(int fd);
功能:关闭 一个文件描述符
参数:fd:要关闭的文件描述符
返回值:成功返回0,失败返回-1,并设置errno
头文件:#include <unistd.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:连接服务器
参数:sockfd:套接字
addr:服务器的ip,端口等结构体信息
addrlen:addr结构体大小
返回值:成功返回0,失败返回-1,并设置errno
头文件:#include <sys/socket.h>
pid_t fork(void);
功能:创建一个子进程
参数:无
返回值:父进程返回子进程的进程id,子进程返回0
头文件:#include <unistd.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:设置信号集屏蔽字
参数:how:SIG_BLOCK (mask |= set),SIG_UNBLOCK (mask &= ~set),SIG_SETMASK (mask = set)
set:信号集
oldset:旧的信号集
返回值:成功返回0,失败返回-1,并设置errno
头文件:#include <signal.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收已结束的子进程资源
参数:pid:pid<-1 等待进程组识别码为pid绝对值的任何子进程
pid=-1 等待任何子进程, 相当于wait()
pid=0 等待进程组识别码与目前进程相同的任何子进程
pid>0 等待任何子进程识别码为pid的子进程
status:子进程的结束状态值
options:WNOHANG 如果没有任何已经结束的子进程则马上返回, 不予以等待
WUNTRACED 如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会
返回值:成功返回子进程pid,失败返回-1
头文件: #include <sys/types.h>
#include <sys/wait.h>
服务端代码
-
#include <stdio.h>
-
#include <sys/socket.h>
-
#include <arpa/inet.h>
-
#include <string.h>
-
#include <unistd.h>
-
#include <signal.h>
-
#include <stdlib.h>
-
#include <sys/types.h>
-
#include <sys/wait.h>
-
#include <errno.h>
-
-
#define BUF_SIZE 256
-
-
void do_sigchld(int signo, siginfo_t *siginfo, void *p)
-
{
-
int status;
-
pid_t pid;
-
while((pid =
waitpid(
0, &status, WNOHANG)) >
0)
//0:回收跟调用进程同组的子进程的资源,WNOHANG:不阻塞
-
{
-
if (
WIFEXITED(status))
-
printf(
"pid = %d, parent pid = %d, child pid = %d, exit status %d\n", pid,
getpid(), siginfo->si_pid,
WEXITSTATUS(status));
-
else
if (
WEXITSTATUS(status))
-
printf(
"pid = %d, parent pid = %d, child pid = %d, exit by signal %d\n ", pid,
getpid(), siginfo->si_pid,
WIFSIGNALED(status));
-
}
-
}
-
-
void sys_err(const char *str, int err)
-
{
-
perror(str);
-
exit(err);
-
}
-
-
int main(int argc, char *argv[])
-
{
-
if (argc <
2)
-
{
-
printf(
"%s port\n", argv[
0]);
-
exit(
1);
-
}
-
//创建流式套接字
-
int lfd =
socket(AF_INET, SOCK_STREAM,
0);
-
if (lfd <
0)
-
sys_err(
"socket",
1);
-
-
//绑定ip端口
-
struct
sockaddr_in server_addr;
-
server_addr.sin_family = AF_INET;
-
server_addr.sin_port =
htons((
unsigned
short)
atoi(argv[
1]));
-
server_addr.sin_addr.s_addr =
0;
//0表示将本机所有ip都绑定上
-
int ret =
bind(lfd, (
struct sockaddr *)&server_addr,
sizeof(server_addr));
-
if (ret <
0)
-
sys_err(
"bind",
1);
-
-
//监听
-
ret =
listen(lfd,
128);
-
if (ret <
0)
-
sys_err(
"listen",
1);
-
-
//阻塞SIGCHLD信号
-
sigset_t set;
-
sigemptyset(&set);
-
sigaddset(&set, SIGCHLD);
-
sigprocmask(SIG_BLOCK, &set,
NULL);
-
int block =
1;
-
//循环提取
-
int cfd;
-
pid_t pid;
-
struct
sockaddr_in client_addr;
-
socklen_t len =
sizeof(client_addr);
-
char buf[BUF_SIZE];
-
ssize_t size;
-
char ip[INET_ADDRSTRLEN] =
"";
-
while(
1)
-
{
-
cfd =
accept(lfd, (
struct sockaddr *)&client_addr, &len);
-
if (cfd <
0)
-
{
-
if (errno == EINTR)
-
continue;
-
-
sys_err(
"accept",
1);
-
}
-
-
printf(
"client ip = %s, port = %d connect success\n",
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip,
sizeof(ip)),
ntohs(client_addr.sin_port));
-
pid = fork();
-
if (pid ==
0)
-
{
-
//in child
-
close(lfd);
//关闭不用了的监听套接字
-
//解除阻塞SIGCHLD信号
-
sigprocmask(SIG_UNBLOCK, &set,
NULL);
-
block =
0;
-
-
while(
1)
-
{
-
memset(buf,
0,
sizeof(buf));
-
size =
read(cfd, buf,
sizeof(buf));
-
if (size ==
0)
//客户端断开连接
-
{
-
printf(
"client close\n");
-
break;
-
}
-
-
printf(
"%s\n", buf);
-
write(cfd, buf, size);
-
}
-
-
break;
-
}
-
else
if (pid >
0)
-
{
-
//in parent
-
close(cfd);
//关闭不用了的跟客户端通讯的套接字
-
if (
1 == block)
-
{
-
//先捕捉
-
struct
sigaction sa;
-
sa.sa_sigaction = do_sigchld;
-
sigemptyset(&sa.sa_mask);
-
sa.sa_flags = SA_SIGINFO;
-
sigaction(SIGCHLD, &sa,
NULL);
-
//后解除阻塞
-
sigprocmask(SIG_UNBLOCK, &set,
NULL);
-
block =
0;
-
}
-
}
-
else
-
sys_err(
"fork",
1);
-
}
-
-
//关闭套接字
-
if (pid ==
0)
-
close(cfd);
-
else
if (pid >
0)
-
close(lfd);
-
-
return
0;
-
}
客户端代码
-
#include <stdio.h>
-
#include <unistd.h>
-
#include <sys/socket.h>
-
#include <arpa/inet.h>
-
#include <stdlib.h>
-
#include <string.h>
-
-
int main(int argc, char *argv[])
-
{
-
if (argc <
3)
-
{
-
printf(
"%s sever_ip server_port\n", argv[
0]);
-
exit(
1);
-
}
-
-
//创建流式套接字
-
int fd =
socket(AF_INET, SOCK_STREAM,
0);
-
if (fd <
0)
-
{
-
perror(
"socket");
-
exit(
1);
-
}
-
-
//连接服务器
-
struct
sockaddr_in addr;
-
addr.sin_family = AF_INET;
-
addr.sin_port =
htons((
unsigned
short)
atoi(argv[
2]));
-
addr.sin_addr.s_addr =
inet_addr(argv[
1]);
-
int ret =
connect(fd, (
struct sockaddr *)&addr,
sizeof(addr));
-
printf(
"connect ret = %d\n", ret);
-
if (ret <
0)
-
{
-
perror(
"connect");
-
exit(
1);
-
}
-
-
//读写数据
-
char buf_w[
128] =
"";
-
char buf_r[
128] =
"";
-
ssize_t size_r =
0;
-
while(
1)
-
{
-
fgets(buf_w,
sizeof(buf_w), stdin);
-
buf_w[
strlen(buf_w) -
1] =
0;
-
write(fd, buf_w,
strlen(buf_w));
-
size_r =
read(fd, buf_r,
sizeof(buf_r));
-
if (size_r ==
0)
//服务器断开
-
break;
-
else
-
printf(
"%s\n", buf_r);
-
-
memset(buf_w,
0,
sizeof(buf_w));
-
memset(buf_r,
0,
sizeof(buf_r));
-
}
-
-
//关闭套接字
-
close(fd);
-
-
return
0;
-
}
结果
3个客户端连接服务器时
服务端
客户端
ps aux 可以看到有4个服务器进程(其中一个是主进程,用于监听处理新的连接,其他3个子进程用于跟客户端进行通信)
回射服务信息截图
客户端1:
客户端2:
客户端3:
服务器收到进行打印:
3个客户端都退出时
服务端收到客户端退出,并回收跟客户端进行通信的3个子进程的资源
ps aux 查看可以看到只剩主进程,用于监听处理新的连接,其他3个子进程都已结束
转载:https://blog.csdn.net/d704791892/article/details/129168681