有关于Linux socket网络编程的具体细则笔者在这先不展开,整个通信简单来说就是基于socket套接字可以实现两台设备之间的通信,其中一台设备需要
将其网络的IP协议族、IP地址和端口号绑定到一个创建好的socket
上,然后监听
该socket等待设备连接
,这台设备就是大家所认知的服务器,另外一台设备通过服务器的同样需要将服务器的IP协议族、IP地址和端口号绑定到一个socket,然后基于该socket可以实现和服务器的连接,以上这种方式socket网络编程的大致流程如下:
服务器:
int main(void)
{
int sockfd;
int client_fd;
int ret;
int addrlen;
struct sockaddr_in socket_info;
struct sockaddr_in client_info;
char buf[BUFSIZE];
//创建socket套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket");
exit(0);
}
//绑定服务器信息
memset(&socket_info, 0, sizeof(socket_info));
socket_info.sin_family = AF_INET; //ipv4
socket_info.sin_port = htons(SERVER_PORT); //端口号
socket_info.sin_addr.s_addr = inet_addr(SERVER_IP); //ip地址
ret = bind(sockfd, (struct sockaddr *)&socket_info, sizeof(socket_info));
if(ret < 0){
perror("bind");
exit(0);
}
//设置监听套接字
ret = listen(sockfd, 10);
if(ret < 0){
perror("listen");
exit(0);
}
printf("server waiting client……\r\n");
//等待客户端连接
client_fd = accept(sockfd, (struct sockaddr *)&client_info, &addrlen);
if(client_fd < 0){
perror("accept");
exit(0);
}else{
printf("client information:\r\n");
printf("client socket fd = %d\r\n", client_fd);
printf("client inet:%s \r\n", inet_ntoa(client_info.sin_addr));
printf("client port = %d\r\n", ntohs(client_info.sin_port));
}
//和客户端通信
while(1){
ret = recv(client_fd, buf, BUFSIZE, 0);
if(ret < 0){
perror("recv");
}else if(ret == 0){
printf("client shutdown!\r\n");
exit(0);
}else{
printf("C->S:%s\r\n", buf);
}
ret = send(client_fd, buf, BUFSIZE, 0);
if(ret < 0){
perror("send");
}
}
close(client_fd);
close(sockfd);
}
客户端:
int main(void)
{
int sockfd;
int client_fd;
int ret;
struct sockaddr_in server_info;
struct sockaddr_in client_info;
char buf[BUFSIZE];
//创建socket套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket");
exit(0);
}
//绑定服务器信息
memset(&server_info, 0, sizeof(server_info));
server_info.sin_family = AF_INET; //ipv4
server_info.sin_port = htons(SERVER_PORT); //端口号
server_info.sin_addr.s_addr = inet_addr(SERVER_IP); //ip地址
//连接服务器
ret = connect(sockfd, (struct sockaddr *)&server_info, sizeof(server_info));
if(ret < 0){
perror("connet");
exit(0);
}
//和客户端通信
while(1){
printf("C->S:please input data:\r\n");
read(0, buf, sizeof(buf));
ret = send(sockfd, buf, BUFSIZE, 0);
if(ret < 0){
perror("send");
}
ret = recv(sockfd, buf, BUFSIZE, 0);
if(ret < 0){
perror("recv");
}else if(ret == 0){
printf("server shutdown!\r\n");
exit(0);
}else{
printf("S->C:%s\r\n", buf);
}
memset(buf, 0, BUFSIZE);
}
close(sockfd);
}
那么正常使用情况都是一个服务器对应有多个客户端的,举个最简单的例子:我们登陆的微信号就是一个客户端,通过网络接入腾讯的服务器,那么腾讯的这个微信的服务器就是唯一的,但是我们的微信号可以会对应很多用户。类似于上述情况那么由前面的一个服务器对应一个客户端来通信的情况在实际应用中是不可能存在的,那么一个服务器就需要同时能够处理多个客户端的请求,在Linux中实现这种通信最常见的方式有多进程和多线程,相比于多进程的来说,多线程具有轻量、任务切换快等优点;
多线程服务器使用的核心工作在于,等待客户端连接的工作必须始终能够运行,同时已连接客户端的数据收发功能也需要正常进行
,所以,解决的方案就是每连接上一个客户端就开一个线程,在该线程中处理客户端的事务
,多线程服务器的代码实现如下:
typedef struct{
int s_fd;
struct sockaddr_in s_info;
}client_info_in;
void *thread_client_manage(void *arg);
int main(void)
{
int sockfd;
int client_fd;
pthread_t tid;
int ret;
int addrlen;
struct sockaddr_in socket_info;
client_info_in client_info_t;
char buf[BUFSIZE];
//创建socket套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket");
exit(0);
}
//绑定服务器信息
memset(&socket_info, 0, sizeof(socket_info));
socket_info.sin_family = AF_INET; //ipv4
socket_info.sin_port = htons(SERVER_PORT); //端口号
// socket_info.sin_addr.s_addr = inet_addr(SERVER_IP); //ip地址
socket_info.sin_addr.s_addr = INADDR_ANY; //0.0.0.0 -- 默认找到你能上网的网卡 直接绑定对应的IP
//解决服务重连地址拒绝问题
int reuse=1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); //本地地址可重复使用
ret = bind(sockfd, (struct sockaddr *)&socket_info, sizeof(socket_info));
if(ret < 0){
perror("bind");
exit(0);
}
//设置监听套接字
ret = listen(sockfd, 10);
if(ret < 0){
perror("listen");
exit(0);
}
//和客户端通信
while(1){
printf("server waiting client……\r\n");
//等待客户端连接
client_info_t.s_fd = accept(sockfd, (struct sockaddr *)&client_info_t.s_info, &addrlen);
if(client_fd < 0){
perror("accept");
exit(0);
}else{
printf("client information:\r\n");
printf("client socket fd = %d\r\n", client_fd);
printf("client inet:%s \r\n", inet_ntoa(client_info_t.s_info.sin_addr));
printf("client port = %d\r\n", ntohs(client_info_t.s_info.sin_port));
}
//客户端已连接 创建一个线程 --- 处理此时连接的这个客户端的信息
pthread_create(&tid, NULL, thread_client_manage, (void *)&client_info_t);
}
close(client_fd);
close(sockfd);
}
void *thread_client_manage(void *arg)
{
int ret;
char buf[BUFSIZE];
client_info_in info = *(client_info_in *)arg; //结构体变量整体赋值
pthread_detach(pthread_self()); //将本线程分离 -- 在线程结束直接有系统回收空间
while(1){
memset(buf, 0, BUFSIZE);
//处理客户端事物
ret = recv(info.s_fd, buf, BUFSIZE, 0);
if(ret < 0){
perror("recv");
}else if(ret == 0){
printf("client shutdown!\r\n");
pthread_exit(NULL); //客户端断开后,销毁本线程
}else{
printf("C->S:%d:%s\r\n", info.s_fd, buf);
}
ret = send(info.s_fd, buf, BUFSIZE, 0);
if(ret < 0){
perror("send");
}
if(strstr(buf, "exit") != NULL){
printf("client exit\r\n");
pthread_exit(NULL); //客户端断开后,销毁本线程
}
}
}
运行结果如下:
「艾尔登法环」梅琳娜手办开订 立体手办▪
万代「艾尔登法环」白狼战鬼手办开订 立体手办▪
「夏目友人帐」猫咪老师粘土人开订 立体手办▪
「五等分的新娘∬」中野三玖·白无垢版手办开订 立体手办▪
「海贼王」乌索普Q版手办开订 立体手办▪
良笑社「初音未来」新手办开订 立体手办▪
「黑岩射手DAWN FALL」死亡主宰手办开订 立体手办▪
「盾之勇者成名录」菲洛手办登场 立体手办▪
「魔法少女小圆」美树沙耶香手办开订 立体手办▪
「咒术回战」七海建人粘土人登场 立体手办▪
「五等分的新娘」中野二乃白无垢手办开订 立体手办▪
「为美好的世界献上祝福!」芸芸粘土人开订 立体手办▪
「公主连结 与你重逢」六星可可萝手办开订 立体手办▪
「女神异闻录5」Joker雨宫莲手办开订 立体手办▪
「间谍过家家」约尔・福杰粘土人登场 立体手办▪
「街角魔族 2丁目」吉田优子手办开订 立体手办▪
「火影忍者 疾风传」旗木卡卡西·暗部版粘土人登场 立体手办▪
「佐佐木与宫野」宫野由美粘土人开订 立体手办▪
「盾之勇者成名录」第2季拉芙塔莉雅手办开订 立体手办▪
「咒术回战」两面宿傩Q版坐姿手办开订 立体手办▪
「DATE·A·BULLET」时崎狂三手办开订 立体手办▪
「狂赌之渊××」早乙女芽亚里粘土人开订 立体手办▪
「魔道祖师」魏无羨粘土人开订 立体手办▪
「新·奥特曼」奥特曼手办现已开订 立体手办▪