循环服务器
一次只能处理一个客户端,等这个客户端退出后,才能处理下一个客户端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define ERR_LOG(msg) do{\
perror(msg);\
printf("%d %s %s\n", __LINE__, __func__, __FILE__);\
}while(0)
#define N 128
#define PORT 7777
#define IP "192.168.1.102"
int main(int argc, const char *argv[])
{
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
ERR_LOG("socket");
return -1;
}
//2.绑定服务器ip和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) <0)
{
ERR_LOG("bind");
return -1;
}
//3.将套接字设置为被动监听状态
if(listen(sfd, 5) < 0)
{
ERR_LOG("listen");
return -1;
}
printf("监听套接字成功\n");
//4.获取连接后的套接字
struct sockaddr_in cin;
socklen_t clen = sizeof(cin);
while(1)
{
int newfd = accept(sfd, (struct sockaddr*)&cin, &clen);
if(newfd < 0)
{
ERR_LOG("accept");
return -1;
}
printf("newfd = %d\n", newfd);
//5.循环发送接收
int res = 0;
char buf[N] = "";
while(1)
{
bzero(buf, N);
res = recv(newfd, buf, N, 0);
if(res < 0)
{
ERR_LOG("recv");
return -1;
}
else if(0 == res)
{
printf("对方关闭\n");
break;
}
printf("%s\n", buf);
strcat(buf, "*_*");
if((res=send(newfd, buf, N, 0)) < 0)
{
ERR_LOG("send");
return -1;
}
printf("发送成功%d\n", res);
}
close(newfd);
}
//6.关闭套接字
close(sfd);
return 0;
}
并发服务器
- 可以同时处理多个客户端请求,创建子进程或者分支线程来处理客户端的请求
- 父进程/主线程只负责连接,子进程/分支线程只负责与客户端进行交互
多进程并发服务器
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#define ERR_LOG(msg) do{\
perror(msg);\
printf("%d %s %s\n", __LINE__, __func__, __FILE__);\
}while(0)
#define N 128
#define PORT 8888
#define IP "0.0.0.0"
typedef void (*sighandler_t)(int);
void handler(int sig)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, const char *argv[])
{
//注册信号,回收子进程
sighandler_t s = signal(SIGCHLD, handler);
if(s == SIG_ERR)
{
ERR_LOG("signal");
return -1;
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
ERR_LOG("socket");
return -1;
}
//允许地址快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0)
{
perror("setsockopt");
return -1;
}
//2.绑定服务器ip和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) <0)
{
ERR_LOG("bind");
return -1;
}
//3.将套接字设置为被动监听状态
if(listen(sfd, 5) < 0)
{
ERR_LOG("listen");
return -1;
}
printf("监听套接字成功\n");
//4.获取连接后的套接字
struct sockaddr_in cin;
socklen_t clen = sizeof(cin);
while(1)
{
int newfd = accept(sfd, (struct sockaddr*)&cin, &clen);
if(newfd < 0)
{
ERR_LOG("accept");
return -1;
}
//打印连接成功的客户端信息
char ip[20] = "";
inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
printf("[%s:%d] fd:%d连接成功\n", ip, ntohs(cin.sin_port), newfd);
pid_t pid = fork();
if(pid > 0)
{
//父进程,关闭newfd,因为newfd对于父进程没有作用.
close(newfd);
}
else if(0 == pid)
{
//子进程,与客户端进程交互
close(sfd);
int res = 0;
char buf[N] = "";
while(1)
{
//收发
bzero(buf, N);
res = recv(newfd, buf, N, 0);
if(res < 0)
{
ERR_LOG("recv");
exit(1);
}
else if(0 == res)
{
printf("对方关闭\n");
break;
}
printf("%s\n", buf);
strcat(buf, "*_*");
if((res=send(newfd, buf, N, 0)) < 0)
{
ERR_LOG("send");
exit(1);
}
printf("发送成功%d\n", res);
}
//注意,子进程不需要回到accept函数位置,所以直接退出.
exit(1);
}
else
{
ERR_LOG("fork");
return -1;
}
}
//6.关闭套接字
close(sfd);
return 0;
}
多线程并发服务器
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#define ERR_LOG(msg) do{\
perror(msg);\
printf("%d %s %s\n", __LINE__, __func__, __FILE__);\
}while(0)
#define N 128
#define PORT 9999
#define IP "0.0.0.0"
typedef struct
{
int newfd;
struct sockaddr_in cin;
}__cli_info;
int show_info(struct sockaddr_in,int);
void* recv_cli_msg(void* arg);
int main(int argc, const char *argv[])
{
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd < 0)
{
ERR_LOG("socket");
return -1;
}
//本地端口快速重用
int reuse = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
//2.绑定服务器ip和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) <0)
{
ERR_LOG("bind");
return -1;
}
//3.将套接字设置为被动监听状态
if(listen(sfd, 5) < 0)
{
ERR_LOG("listen");
return -1;
}
printf("监听套接字成功\n");
//4.获取连接后的套接字
struct sockaddr_in cin;
socklen_t clen = sizeof(cin);
int newfd = 0;
while(1)
{
newfd = accept(sfd, (struct sockaddr*)&cin, &clen);
if(newfd < 0)
{
ERR_LOG("accept");
return -1;
}
//打印连接成功的客户端信息
show_info(cin, newfd);
//创建分支线程
__cli_info cinfo;
cinfo.newfd = newfd;
cinfo.cin = cin;
pthread_t tid = -1;
if(pthread_create(&tid, NULL, recv_cli_msg, (void*)&cinfo) !=0)
{
ERR_LOG("pthread_create");
return -1;
}
}
//6.关闭套接字
close(sfd);
return 0;
}
//线程处理函数
void* recv_cli_msg(void* arg)
{
//分离线程,线程退出后自动回收线程资源
pthread_detach(pthread_self());
//下面方式是错误的,因为通过指针间接访问的是main函数中的newfd的地址空间内容
//每次连接成功accept后,*pnewfd的值都会更新.
//int* pnewfd = (int*)arg;
__cli_info cli = *(__cli_info*)arg;
int res = 0;
char buf[N] = "";
while(1)
{
bzero(buf, N);
res = recv(cli.newfd, buf, N, 0);
if(res < 0)
{
ERR_LOG("recv");
break;
}
else if(0 == res)
{
printf("对方关闭\n");
break;
}
char ip[20] = "";
inet_ntop(AF_INET, &cli.cin.sin_addr, ip ,20);
printf("[%s:%d]:%s\n", ip, ntohs(cli.cin.sin_port), buf);
}
close(cli.newfd);
pthread_exit(NULL);
}
//打印客户端信息
int show_info(struct sockaddr_in cin, int newfd)
{
char ip[20] = "";
if(inet_ntop(AF_INET, &cin.sin_addr, ip, 20)<0)
{
ERR_LOG("inet_ntop");
return -1;
}
printf("[%s:%d] fd:%d连接成功\n", ip,ntohs(cin.sin_port), newfd);
return 0;
}
本文链接:https://shengto.top/network_programming/tcp_concurrency_servermodel.html
转载时须注明出处及本声明