服务器端做的事情:
1.创建监听的套接字。
int socket(int domain, int type, int protocol)
2.绑定。将监听所用的套接字绑定相关IP端口信息。
这里存储IP和端口的结构体有专用地址和通用地址,在我们自己构建的时候常常使用的是专用地址,而在具体使用的时候都要转化为通用地址。比如这里的专用地址有sockaddr_in (代表IPv4),sockaddr_in6(代表IPv6),sockaddr_un(Unix本地协议族)。然后通用地址为sockaddr。然后我们非常常用的就是一个sockaddr_in结构体,以IPv4为例。然后有不懂的结构体可以直接在Linux系统中查看man文档(比如man 2 socket, man 2 bind),并且有这些专门的结构体定义及所用的头文件。
struct sockaddr_in
{
sa_family_t sin_family; /* __SOCKADDR_COMMON (sin_) */
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
struct in_addr
{
in_addr_t s_addr;
};
对于sockaddr_in来说,也就是IPv4地址,要记录三部分内容,第一个是协议族,常用的也就是前面提到的AF_INET,第二部分为接受的IP地址,当选用INADDR_ANY时意味着接受任意的IP地址,第三个为使用的端口,htons代表着host to net short,也就是unsigned short的主机字节序到网络字节序的转换,两个字节的转换,端口号恰好可以用两个字节16位的大小来存储。同理ntohl代表的是net to host long,也就是long类型的数据从网络字节序转换到主机字节序,至于long类型,恰好对应的是IP地址32位,四个字节。至于上面的第四个参数,是用于结构体内存对齐的,就不用管了。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
3.监听。这里的绑定是监听所绑定的套接字。
int listen(int sockfd, int backlog);
4.接受客户端连接。接受连接所用的函数是accept,如果没有客户端连接进入,那么就不会执行下面的代码,也就是不会执行下面的具体通信,是阻塞在那的。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.通信(通常情况:先recv后send)
服务器端通常的一个通信流程通常都是先接收到客户端请求的信息,然后再发送对应的消息给客户端。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
6.释放资源
主要是监听所用的套接字和连接所用的套接字。
//创建TCP服务器端
//时间:2022年3月26日15:30:18
//作者:credic_1017
//sever.c
#include
#include
#include
#include
#include
int main(){
//1.创建socket(用于监听的套接字)
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if ( lfd == -1 ){
perror("socket");
exit(-1);
}
//2.绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
//inet_pton(AF_INET, "192.168.204.128",saddr.sin_addr.s_addr);
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(9999);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if ( ret == -1 ){
perror("bind");
exit(-1);
}
//3.监听
ret = listen(lfd, 8);
if ( ret == -1 ){
perror("listen");
exit(-1);
}
//4.接收客户端连接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
if( cfd == -1 ){
perror("accept");
exit(-1);
}
//输出客户端信息
char clientIP[16];
inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
unsigned short clientPort = ntohs(clientaddr.sin_port);
printf("client ip is: %s, port is %d\n", clientIP, clientPort);
//5.获取客户端信息
char recvBuf[1024] = {0};
char data[1024] = {0};
while(1){
int recvlen = read(cfd, recvBuf, sizeof(recvBuf));
sleep(1);
if( recvlen == -1 ){
perror("read");
exit(-1);
}else if(recvlen > 0){
printf("rece client data: %s\n", recvBuf);
}else if(recvlen == 0){
//表示客户端断开连接
printf("client closed\n");
}
//给客户端发送数据
fgets(data, 1024 , stdin);
data[1023] = '\0';
write(cfd, data, strlen(data));
}
//关闭文件描述符
close(lfd);
close(cfd);
return 0;
}
1.创建套接字
2.连接服务器端
首先创建服务器套接字,然后填写服务器IP端口,注意IP和端口都得做一个本地字节序到网络字节序的转换。这里端口进行字节序转换使用的函数是htons,而IP使用的是inet_pton,主要是由于IP地址常用点分十进制的记法,而端口则直接是一个整数,如果是一个大整数则可以用htonl函数,具体含义在前面的服务器端解析已经说过了。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
3.通信(通常先send再recv)
作为客户端通常先发送消息给服务器端,之后再接收来自服务器端的消息。具体流程和上面服务器端通信类似。
4.断开连接释放资源
通常而言是客户端断开连接的,当然服务器端先断开连接也是可以的。释放资源主要是释放套接字(文件描述符)。
//创建TCP客户端
//时间:2022年3月27日15:13:07
//作者:credic_1017
//client.c
#include
#include
#include
#include
#include
int main(){
//1.创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
//2.连接服务器端
struct sockaddr_in severaddr;
severaddr.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.204.128", &severaddr.sin_addr.s_addr);
severaddr.sin_port = htons(9999);
int ret = connect(fd, (struct sockaddr *)&severaddr, sizeof(severaddr));
if (ret == -1){
perror("connect");
exit(-1);
}
//3.通信
char recvBuf[1024] = {0};
char data[1024] = {0};
while(1){
fgets(data, 1024 , stdin);
data[1023] = '\0';
write(fd, data, strlen(data));
sleep(1);
int len = read(fd, recvBuf, sizeof(recvBuf));
if( len == -1 ){
perror("read");
exit(-1);
}else if(len > 0){
printf("rece sever data: %s\n", recvBuf);
}else if(len == 0){
//表示客户端断开连接
printf("sever closed...\n");
}
}
close(fd);
return 0;
}
目前的通信是一对一的,单个进程对单个进程,也就是上面的通信服务器端也面对两个及两个以上客户端的连接是无法进行处理的。在两个连接的情况下,一个客户端资源已经释放,第二个客户端依旧不能与服务器端进行通信。这是一个很大的弊端,还在学习多线程及多进程进行服务器端进行高并发,能够较好的解决这个问题。