一、一个服务器连接一个客户端
1、步骤
- socket() 创建套接字
- bind() 绑定ip和端口
- listen() 监听套接字,并创建监听队列
- int cfd = accept(); 接收连接,并使用cfd表示对方
- 读写数据
2、代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
int main()
{
//创建监听套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
//绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
//addr.sin_addr.s_addr = INADDR_ANY; //绑定的是通配地址
inet_pton(AF_INET, "192.168.88.129", &addr.sin_addr.s_addr); // 设置IP地址,需要转为大端
bind(lfd, (struct sockaddr *)&addr, sizeof(addr)); // 绑定ip地址和端口
//监听
listen(lfd,128); // 设置最大监听队列128
//提取
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); // 从lfd中的监听队列中提取客户端,并用cfd(监听套接字)标识
char ip[16] = "";
printf("new client ip = %s, port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));//打印连接的客户端ip和端口号
//读写
char buf[1024] = "";
while(1)
{
bzero(buf, sizeof(buf));
int n = read(STDIN_FILENO, buf, sizeof(buf)); // 从标准输入库中读取数据
write(cfd, buf, n); // 写数据
n = read(cfd, buf, sizeof(buf)); // 读数据
if(0 == n)
{
printf("客户端关闭\n");
exit(0);
}
printf("%s\n", buf);
}
return 0;
}
二、多进程实现并发服务器
注意:这里用到上一篇文章的tcp包裹函数
1、步骤:
创建监听套接字
绑定
监听
while(1)
{
提取连接
fork创建子进程
子进程中,关闭lfd,服务客户端
父进程关闭cfd,回收子进程的资源
}
关闭
2、代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<signal.h>
#include "wrap.h"
// 父进程非阻塞回收子进程资源
void free_process(int sig)
{
pid_t pid;
while(1)
{
pid = waitpid(-1, NULL, WNOHANG); // -1表示所有子进程
if(pid <= 0) // <0,子进程全部退出了;=0没有进程退出
{
break;
}
}
}
int main()
{
// 屏蔽SIGCHLD信号
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
// 创建监听套接字,并绑定
int lfd = tcp4bind(8000,NULL);
// 监听
Listen(lfd, 128);
struct sockaddr_in cliaddr; // 客户端信息结构体
socklen_t len = sizeof(cliaddr);
// 父进程接收连接
pid_t pid;
char ip[16];
int cfd;
while(1)
{
// 提取连接
cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
printf("new client ip = %s, port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
ntohs(cliaddr.sin_port));
pid = fork();
if(pid < 0)
{
perror("fork");
exit(0);
}
else if(pid == 0)// 子进程
{
// 关闭监听lfd
close(lfd);
break;
}
else // 父进程
{
close(cfd); // 父进程关闭通信
// 回收
struct sigaction act;
act.sa_handler = free_process; // 回收函数
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL); // 注册
sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除屏蔽
}
}
if(pid == 0)
{
char buf[1024];
while(1)
{
int n = read(cfd, buf, sizeof(buf));
if(n < 0)
{
perror("read error:");
close(cfd);
exit(0);
}
else if(n == 0)
{
printf("client close\n");
close(cfd);
exit(0);
}
else
{
printf("%s", buf);
write(cfd, buf, n); // 读取成功之后,写同样的消息给客户端
}
}
}
return 0;
}
三、线程版并发服务器
// 客户端信息结构体
typedef struct c_info
{
int cfd;
struct sockaddr_in cliaddr;
}CINFO;
// 线程执行函数
void * client_fun(void *arg);
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("argc < 2 \n .a.out 8000 \n");
}
short port = atoi(argv[1]);
int lfd = tcp4bind(port, NULL); // 创建套接字 绑定
Listen(lfd, 128);
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
CINFO *info;
// 主线程循环接收
while(1)
{
int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
char ip[16];
pthread_t pthid;
info = malloc(sizeof(CINFO));
info->cfd = cfd;
info->cliaddr = cliaddr;
pthread_create(&pthid, NULL, client_fun, info);
}
return 0;
}
void * client_fun(void *arg)
{
char ip[16];
CINFO * info = (CINFO *)arg;
printf("new client ip = %s; port = %d\n", inet_ntop(AF_INET, &(info->cliaddr.sin_addr.s_addr), ip, 16),
ntohs(info->cliaddr.sin_port));
while(1)
{
char buf[1024] = "";
int count = 0;
count = read(info->cfd, buf, sizeof(buf));
if(count < 0)
{
perror("read error:");
break;
}
else if(count == 0)
{
printf("client close\n");
break;
}
else
{
printf("%s\n", buf);
write(info->cfd, buf, count);
}
}
close(info->cfd);
free(info);
}