客户端跟服务端通信流程
服务端流程步骤
socket函数创建监听套接字lfd;
bind函数将监听套接字绑定ip和端口;
listen函数将服务器设置为被动监听状态,同时创建一条未完成连接队列(没走完tcp三次握手流程的连接),和一条已完成连接队列(已完成tcp三次握手的连接);
accept函数循环的从已完成连接的队列中提取连接,并返回一个新的套接字cfd跟客户端进行通信;
pthread_create函数创建一个子线程,跟客户端进行通信;
子线程:read函数循环的从r缓冲区读取客户端发送的数据,write函数将要发送给客户端的数据写入w缓冲区;
close函数关闭套接字;
客户端流程步骤
socket函数创建套接字;
connect函数连接服务器;
write函数将要发送给服务端的数据写入w缓冲区,read函数从r缓冲区读取服务器发送给客户端的数据;
close函数关闭套接字;
相关函数
#include <sys/socket.h>
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/types.h>
#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/socket.h>
int listen(int s, int backlog);
功能:让服务器处于被动监听状态,同时创建了一条未完成三次握手的连接队列和一条已经完成三次握 手的连接队列
参数:s:套接字
backlog:支持未完成连接和已完成连接之和的最大值,一般设置128
返回值:成功返回0,失败返回-1,并设置errno
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
功能:从已完成连接队列中提取客户端连接
参数:s:套接字
addr:存放成功连接的客户端的ip,端口等信息结构体
addrlen:存放addr结构体大小的变量地址
返回值:成功则返回一个非负整数标识这个连接套接字,是否返回-1
#include <unistd.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 <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:连接服务器
参数:sockfd:套接字
addr:服务器的ip,端口等结构体信息
addrlen:addr结构体大小
返回值:成功返回0,失败返回-1,并设置errno
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建子线程
参数:thread:线程id
attr:线程属性,对属性没要求,默认的话,传NULL即可
start_routine:线程回调函数
arg:需要传进线程回调函数的参数
返回值:成功返回0,失败返回错误码
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:设置线程为分离态,线程退出时自己回收资源
参数:thread:线程id
返回值:成功返回0,失败返回错误码
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#define BUF_SIZE 256
typedef struct _clientinfo
{
int fd;
struct sockaddr_in client_addr;
}clientinfo;
void sys_err(const char *str, const int exitno)
{
perror(str);
exit(exitno);
}
void sys_errex(const char *str, const int exitno, const int err)
{
fprintf(stderr, "%s:%s\n", str, strerror(err));
exit(exitno);
}
void *t_client(void *arg)
{
clientinfo *client = (clientinfo *)arg;
char buf[BUF_SIZE] = "";
int size;
char ip[INET_ADDRSTRLEN] = "";
printf("client: %s, port = %d connect success\n", inet_ntop(AF_INET, &client->client_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client->client_addr.sin_port));
while(1)
{
size = read(client->fd, buf, sizeof(buf));
if (size < 0)
{
if (EINTR == errno) //被信号中断不能退出
continue;
sys_err("read error", 1);
}
else if (size == 0) //客户端退出
{
printf("client: %s, port = %d exit\n", ip, ntohs(client->client_addr.sin_port));
break;
}
printf("client: %s, port = %d say: %s\n", ip, ntohs(client->client_addr.sin_port), buf);
while(write(client->fd, buf, size) == -1)
{
if (EINTR == errno) //被信号中断不能退出
continue;
sys_err("write error", 1);
}
memset(buf, 0, sizeof(buf));
}
close(client->fd);
free(client);
printf("thread %lu exit success\n", pthread_self());
}
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("%s port\n", argv[0]);
exit(1);
}
//创建监听socket
int lfd;
int cfd;
int ret;
int optval = 1;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
pthread_t tid;
lfd= socket(AF_INET, SOCK_STREAM, 0);
if (lfd < 0)
sys_err("socket error", 1);
//设置端口复用,解决 address already user 问题
ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(optval));
if (ret < 0)
sys_err("setsockopt error", 1);
//绑定ip, 端口
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都绑定上
ret = bind(lfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret < 0)
sys_err("bind error", 1);
//监听
ret = listen(lfd, 128); //指定未完成连接队列的最大长度为128
if (ret < 0)
sys_err("listen error", 1);
printf("server start success\n");
while(1)
{
cfd = accept(lfd, (struct sockaddr *)&client_addr, &len);
if (cfd < 0)
{
if (ECONNABORTED == errno || EINTR == errno) //软件引起的连接中止或被信号中断,不能退出
continue;
sys_err("accept error", 1);
}
clientinfo *p = malloc(sizeof(clientinfo));
if (!p)
fprintf(stderr, "malloc error\n");
p->fd = cfd;
p->client_addr = client_addr;
ret = pthread_create(&tid, NULL, t_client, (void *)p);
if (0 != ret)
sys_errex("pthread_create error", 1, ret);
ret = pthread_detach(tid); //设置为分离态,线程结束时自己回收资源
if (0 != ret)
sys_errex("pthread_detach error", 1, ret);
}
close(lfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 256
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));
if (ret < 0)
{
perror("connect");
exit(1);
}
printf("connect server success\n");
//读写数据
char buf_w[BUF_SIZE] = "";
char buf_r[BUF_SIZE] = "";
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 -eLf查看LWP,服务端有4条线程(1条监听线程,3条与连接上的客户端通信的线程)
3个客户端分别发一条消息给服务器,服务器接收到,并发回到客户端
3个客户端都退出
此时,ps -eLf查看LWP,服务器只剩1条监听线程
ending😃