概要
学习记录,代码解析
服务器为每个客户端连接请求创建一个线程来进行通信。
优点:逻辑清晰
缺点:线程量较大,很耗内存
一、监听端口
1.建立socket
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
socket函数:
int socket(int domain, int type, int protocol)
domain:套接字要使用的协议簇,协议簇的在“linux/socket.h”里有详细定义,常用的协议簇有:
AF_UNIX(本机通信)
AF_INET(TCP/IP – IPv4)
AF_INET6(TCP/IP – IPv6)
type:套接字类型,常用的类型有:
SOCK_STREAM(TCP流)
SOCK_DGRAM(UDP数据报)
SOCK_RAW(原始套接字)
protocol:协议
协议“protocol”一般设置为“0”,就是说在已经确定套接字使用的协议簇和类型时,这个参数的值就为0。
但是有时候创建初始套接字时,在domain参数未知情况下,即不清楚协议簇和类型时,protocol参数可以用来确定协议的种类。
2.定义地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址
sockaddr在头文件#include <sys/socket.h>中定义, sockaddr的缺陷是: sa_data把目标地址和端口信息混在一起了
struct sockaddr {
sa_family_t sin_family; //地址族
char sa_data[14]; //套接字中的目标地址和端口信息
};
struct sockaddr_in{
sa_family_t sin_family; //地址簇
unit16_t sin_port; //16位TCP/UDP端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用
};
struct in_addr{
In_addr_t s_addr; //32位IPv4地址
};
htons, htonl: 作用是将端口号由主机字节序转换为网络字节序的整数值
(host to net short, host to net long)
3.绑定socket和地址
if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(sever_addr))==-1){
perror("bind error");
return -1;
}
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd: 需要绑定的socket
addr: 存放了服务端用于通信的地址和端口
addrlen: 表示addr结构体的大小
4.监听socket
listen(socketfd, 10);
二、连接处理
5.建立连接,并为每个连接建立一个线程
while(1){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &len);
pthread_t thread_id;
pthread_create(&thread_id, NULL, client_thread, &clientfd);
}
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//成功返回一个新的socket,用于和客户端通信,失败返回-1
sockfd: 文件描述符,被监听的socket
addr: 返回链接客户端地址信息,含IP地址和端口号
addrlen: 传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
int pthread_create(
pthread_t *restrict tidp, //指向线程标识符的指针,用pthread_t创建
const pthread_attr_t *restrict attr, //设置线程属性,默认为NULL
void *(*start_rtn)(void *), //线程运行函数的起始地址
void *restrict arg //无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。
);
6.线程函数,与客户端进行通信
void* client_thread(void* client){
int clientfd=*(int*) client;
while(1){
char buffer[128];
int count=recv(clientfd, buffer, 128, 0);
if(count==0){
close(clientfd);
return NULL;
}
send(clientfd, buffer, count, 0);
}
}
recv函数: 阻塞式等待连接的另一端发送数据。如果另一端close掉了会返回0
int recv(SOCKET sockfd, char *buf, int len, int flags);
sockfd: 指定接收端套接字描述符
buf: 接收数据的缓存区
len: buf的长度
flags: 一般置0
服务器端代码
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>
void* client_thread(void* client){
int clientfd=*(int*) client;
while(1){
char buffer[128];
int count=recv(clientfd, buffer, 128, 0);
if(count==0){
close(clientfd);
return NULL;
}
send(clientfd, buffer, count, 0);
}
}
int main(){
//创建socket,返回文件描述符
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
//定义地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))==-1){
perror("bind error");
return -1;
}
listen(socketfd, 10);
while(1){
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &len);
pthread_t thread_id;
pthread_create(&thread_id, NULL, client_thread, &clientfd);
}
}
客户端代码
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>
int main(){
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in target_addr;
memset(&target_addr, 0, sizeof(sockaddr_in));
target_addr.sin_family=AF_INET;
target_addr.sin_port=htons(8888);
target_addr.sin_addr.s_addr=htonl(INADDR_ANY);
if(connect(clientfd, (struct sockaddr*)&target_addr, sizeof(target_addr))==-1){
perror("connect error");
return -1;
}
char message[]="hello this is client";
send(clientfd, message, sizeof(message), 0);
char buffer[128];
memset(buffer, 0, sizeof(buffer));
int ret=recv(clientfd, buffer, 128, 0);
if(ret>0){
printf("from server message: %s\n", buffer);
}
close(clientfd);
}
参考内容:
https://blog.csdn.net/cfm908826/article/details/109403227
https://blog.csdn.net/will130/article/details/53326740