记录服务端socket流程以备用
该程序可见文末
1.创建socket 文件描述符,并且判断是否成功,失败返回-1
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
return EXIT_FAILURE;
}
2. 将文件描述符和IP端口信息绑定
先创建sockaddr_in
struct sockaddr_in addr;
sockaddr_in的结构体内的数据:
-
sin_family:地址族,通常设置为AF_INET表示IPv4协议。
-
sin_port:端口号,以网络字节序表示。
-
sin_addr:IP地址,以网络字节序表示。(sin.addr 的数据类型是struct in_addr;因此使用时会再次深入到第二个结构体中)
-
sin_zero:填充字段,通常设置为0。
in_addr结构体内的数据 s_addr
给sockaddr_in内部数据赋值,绑定端口与ip
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("192.168.1.1"); //绑定固定ip
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 表示任意可用IP
addr.sin_port = htons(port); // 转换成网络字节序(大端字节序)
秒懂 ! 字节序转换 htons()、ntohl()、ntohs()、htons()(内含详细解析~~)-CSDN博客
其中用到的htons与htonl可以在在里面了解
INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
使用bind绑定
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1) {
perror("bind error");
return EXIT_FAILURE;
}
bind函数用于把某种形式的参数列表与已知的函数进行绑定,形成新的函数
int bind(int __fd, const sockaddr *__addr, socklen_t __len)
这里将我们之前创建的文件描述符lfd和sockaddr_in addr绑定起来
3.监听文件描述符
if ((ret = listen(lfd, 128)) == -1) {
perror("listen error");
return EXIT_FAILURE;
}
printf("[%d]The server is running at %s:%d\n", getpid(), inet_ntoa(addr.sin_addr), port);
int listen(int sockfd, int backlog);
1.sockfd:一个已绑定未被连接的套接字描述符
2.backlog: 规定了内核应该为相应套接字排队的最大连接个数。用SOMAXCONN则为系统给出的最大值
printf输出服务器的运行信息,包括进程ID、IP地址和端口号
4.接受一个socket连接
accept接受连接
// 接受一个socket连接(从已连接队列中获取一个连接进行服务),并返回连接文件描述符。
struct sockaddr_in clientAddr; // 输入参数
socklen_t clientAddrLen = sizeof(clientAddr); // 同时作为输入和输出参数
int cfd = accept(lfd, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (cfd == -1) {
perror("accept error");
return EXIT_FAILURE;
}
char clientIP[16];
memset(clientIP, 0x00, sizeof(clientIP));
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)); // 将网络字节序的整数IP转换成主机字节序的点分十进制字符串
int clientPort = ntohs(clientAddr.sin_port); // 将网络字节序转换成主机字节序
printf("Accept client: %s:%d\n", clientIP, clientPort);
- 客户端使用
connect()
函数连接到服务器的IP地址和端口。 - 服务器通过
accept()
函数接受连接请求,创建一个新的套接字,然后可以使用这个新套接字与客户端进行通信。
这里是服务端,所以用accept接受
//accept的用法
accept(
SOCKET s,
struct sockaddr FAR * addr,
int FAR * addrlen
);
函数的第一个参数用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字)
第二个参数是用来保存客户端套接字对应的“地方”(包括客户端IP和端口信息等)
第三个参数是“地方”的占地大小。返回值对应客户端套接字标识
使用memset将clistenIP初始化,填满0x00
使用inet_ntop将网络字节序的整数IP转换成主机字节序的点分十进制字符串,并存储在clistenIP
数组中。
5.实现读写
// 5、读写连接
char buf[BUFSIZ];
ssize_t size;
for (;;) {
// 初始化buffer
memset(buf, 0x00, sizeof(buf));
// 读取客户端信息
size = read(cfd, buf, sizeof(buf));
if (size == 0) { // zero indicates end of file
printf("The client is closed\n");
break;
}
if (size == -1) {
perror("read error");
continue;
}
printf("read: %s\n", buf);
for (int i = 0; i < strlen(buf); i++) {
buf[i] = toupper(buf[i]);
}
// 发送信息给客户端
size = write(cfd, buf, strlen(buf));
if (size == -1) {
perror("write error");
continue;
}
printf("write: %s\n", buf);
}
read函数:ssize_t read(int fd, void *buf, size_t nbytes);
read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。
write同理
结束记得close两个文件描述符
close(lfd);
close(cfd);
下面是该程序的代码
// server.c
#include <arpa/inet.h>
#include <ctype.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s port\n", argv[0]);
return EXIT_FAILURE;
}
int port = atoi(argv[1]);
// 1、创建监听用的文件描述符
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
return EXIT_FAILURE;
}
// 2、将监听文件描述符和IP端口信息绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 表示任意可用IP
addr.sin_port = htons(port); // 转换成网络字节序(大端字节序)
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1) {
perror("bind error");
return EXIT_FAILURE;
}
// 3、监听文件描述符
if ((ret = listen(lfd, 128)) == -1) {
perror("listen error");
return EXIT_FAILURE;
}
printf("[%d]The server is running at %s:%d\n", getpid(), inet_ntoa(addr.sin_addr), port);
// 4、接受一个socket连接(从已连接队列中获取一个连接进行服务),并返回连接文件描述符。
struct sockaddr_in clientAddr; // 输入参数
socklen_t clientAddrLen = sizeof(clientAddr); // 同时作为输入和输出参数
int cfd = accept(lfd, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (cfd == -1) {
perror("accept error");
return EXIT_FAILURE;
}
char clientIP[16];
memset(clientIP, 0x00, sizeof(clientIP));
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)); // 将网络字节序的整数IP转换成主机字节序的点分十进制字符串
int clientPort = ntohs(clientAddr.sin_port); // 将网络字节序转换成主机字节序
printf("Accept client: %s:%d\n", clientIP, clientPort);
// 5、读写连接
char buf[BUFSIZ];
ssize_t size;
for (;;) {
// 初始化buffer
memset(buf, 0x00, sizeof(buf));
// 读取客户端信息
size = read(cfd, buf, sizeof(buf));
if (size == 0) { // zero indicates end of file
printf("The client is closed\n");
break;
}
if (size == -1) {
perror("read error");
continue;
}
printf("read: %s\n", buf);
for (int i = 0; i < strlen(buf); i++) {
buf[i] = toupper(buf[i]);
}
// 发送信息给客户端
size = write(cfd, buf, strlen(buf));
if (size == -1) {
perror("write error");
continue;
}
printf("write: %s\n", buf);
}
close(lfd);
close(cfd);
printf("The server is shut down\n");
return EXIT_SUCCESS;
}