Linux下的socket操作
一、TCP服务端
创建一个TCP服务器的基本操作:
- 创建一个套接字(socket):使用socket函数
- 绑定套接字(socket):将套接字绑定到一个特定的IP地址和端口号上,这些信息要用结构体sockaddr_in来保存
- 监听请求连接:使用listen函数
- 接受连接:使用accept函数来实现
- 发送和接受信息:一旦建立了连接,服务器和客户端都可以使用套接字的send()和recv()方法来发送和接收数据。发送方使用send()方法将数据发送到套接字,而接收方使用recv()方法从套接字接收数据。
- 关闭连接:当通信完成后,可以调用套接字的close()方法来关闭连接。这将释放套接字占用的资源并终止连接。
1.socket:创建套接字
函数原型:
int socket(int domain, int type, int protocol);
参数说明:
- domain:指定地址族,可以是AF_INET(用于IPv4)或AF_INET6(用于IPv6)。
- type:指定套接字类型,可以是SOCK_STREAM(用于TCP)或SOCK_DGRAM(用于UDP)。
- protocol:一般为0,表示使用默认的协议。
返回值: 如果成功,返回一个非负整数,表示套接字文件描述符(socket file descriptor)。如果失败,返回-1,并设置全局变量errno来指示错误类型。
2. sockaddr_in:sockaddr_in 是一个用于表示 IPv4 地址的结构体,它定义在 <netinet/in.h> 头文件中。
定义如下:
struct sockaddr_in {
sa_family_t sin_family; // 地址族,一般为 AF_INET
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IPv4 地址
char sin_zero[8]; // 用于补齐,一般设置为全0
};
其中,sa_family_t 和 in_port_t 是整数类型,struct in_addr 是一个用于存储 IPv4 地址的结构体。sin_family 表示地址族,一般为 AF_INET,表示使用 IPv4 地址。sin_port 表示端口号,用于标识网络中的应用程序。sin_addr 存储了 IPv4 地址的信息。sin_zero 是一个用于补齐的字段,一般设置为全0。
in_addr:in_addr 是一个用于存储 IPv4 地址的结构体,它定义在 <netinet/in.h> 头文件中。
它的定义如下:
struct in_addr {
in_addr_t s_addr; // IPv4 地址
};
其中,in_addr_t 是一个无符号整数类型,用于存储 IPv4 地址。
3. htons:htons 是一个用于将主机字节序(Host Byte Order)转换为网络字节序(即大端存储)的函数。它定义在 <arpa/inet.h> 头文件中,并且接受一个参数,表示要转换的 16 位无符号整数。
函数原型如下:
uint16_t htons(uint16_t hostshort);
4.bind:bind 是一个用于将一个套接字(socket)与一个特定的地址(包括 IP 地址和端口号)绑定的函数。它通常用于服务器端在监听连接之前将套接字绑定到一个特定的地址上。
在 C 语言中,bind 函数的原型如下:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
- sockfd 是要绑定的套接字的文件描述符。
- addr 是一个指向 sockaddr 结构体的指针,用于指定要绑定的地址信息。
- addrlen 是 addr 所指向的结构体的长度。
返回值:
- 如果 bind 函数执行成功,返回值为 0。这意味着套接字成功地与指定的地址绑定在一起。
- 如果 bind 函数执行失败,返回值为 -1。这表示绑定操作未成功。
5.listen:listen 函数用于将一个已绑定的套接字(socket)设置为监听状态,以便接受传入的连接请求
在 C 语言中,listen 函数的原型如下:
int listen(int sockfd, int backlog);
参数说明:
- sockfd:要设置为监听状态的套接字的文件描述符。
- backlog:定义在连接队列中等待被接受的连接的最大数量。
返回值:
- 如果 listen 函数执行成功,返回值为 0。这意味着套接字已成功设置为监听状态,并且可以开始接受传入的连接请求。
- 如果 listen 函数执行失败,返回值为 -1。这表示设置监听状态的操作未成功。
6.accept:accept 函数用于从已监听的套接字中接受一个传入的连接请求,并创建一个新的套接字来处理该连接。
在 C 语言中,accept 函数的原型如下:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
- sockfd:已监听的套接字的文件描述符。
- addr:指向一个 struct sockaddr 结构的指针,用于存储接受连接的远程地址信息。
- addrlen:指向一个 socklen_t 类型的变量,表示 addr 的大小。
返回值:
- 如果 accept 函数执行成功,返回值为新创建的套接字的文件描述符。这个新套接字用于与客户端进行通信。
- 如果 accept 函数执行失败,返回值为 -1。这表示接受连接请求的操作未成功。
7.recv:recv 函数用于从已连接的套接字接收数据。它是在已建立连接的套接字上进行数据交换的常用函数之一。
在 C 语言中,recv 函数的原型如下:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数含义:
- sockfd:已连接的套接字的文件描述符。
- buf:指向接收数据的缓冲区的指针。
- len:缓冲区的大小,即要接收的最大字节数。
- flags:可选的标志参数,用于控制接收操作的行为。
返回值: recv 函数会阻塞程序执行,直到有数据到达为止。当有数据到达时,recv 函数会将数据从套接字读取到指定的缓冲区 buf 中,并返回实际接收到的字节数。如果返回值为 0,表示对端已关闭连接。如果返回值为 -1,表示接收数据的操作未成功。
8.创建服务器实例
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
//创建socket,参数说明:
//指定协议族:AF_INET(IPv4)和 AF_INET6(IPv6)
//指定套接字使用的协议,通常为0,表示根据 domain 和 type 自动选择合适的协议。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
//绑定
//sockaddr_in 是一个用于表示 IPv4 地址的结构体
struct sockaddr_in server_info; //用于保存服务器的信息(IP PROT)
bzero(&server_info, sizeof(struct sockaddr_in)); //清空,bzero() 是一个函数,用于将指定内存区域的前几个字节设置为零
server_info.sin_family = AF_INET; //地址族
server_info.sin_port = htons(7000); //端口,大于1024都行
// server_info.sin_addr.s_addr = inet_addr("127.0.0.1"); //表示回环IP,用于测试
server_info.sin_addr.s_addr = inet_addr("192.168.175.129");
if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1)
{
perror("bind");
exit(2);
}
//设置监听队列
if(listen(sockfd, 10) == -1)
{
perror("listen");
exit(3);
}
printf("等待客户端的连接...\n");
//接受连接
struct sockaddr_in client_info;
int length = sizeof(client_info);
int fd = accept(sockfd, (struct sockaddr *)&client_info, &length);
if(-1 == fd)
{
perror("accept");
exit(4);
}
printf("接受客户端的连接 %d\n", fd);
char buf[1024] = {0};
ssize_t size;
while(1)
{
size = recv(fd, buf, sizeof(buf), 0);
if(size == -1)
{
perror("recv");
break;
}
else if(size == 0)
break;
if(!strcmp(buf, "bye"))
break;
printf("%s\n", buf);
bzero(buf, 1024);
}
close(fd); //关闭TCP连接,不能在接受数据
close(sockfd); //关闭socket,不能再处理客户端的请求
//socket用于处理客户端的连接,fd用于处理客户端的消息
return 0;
}
二、TCP的客户端
客户端连接服务端的基本操作:
- 创建socket
- 发起连接
- 发送信息
send:send 是一个函数,用于在一个已连接的套接字上发送数据。
函数原型如下:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明:
- sockfd:已连接的套接字描述符。
- buf:指向要发送数据的缓冲区的指针。
- len:要发送的数据的长度(以字节为单位)。
- flags:可选的标志参数,用于控制发送操作的行为。
返回值: 函数返回一个 ssize_t 类型的值,表示实际发送的字节数。如果发送失败,返回值为 -1,并且可以通过检查全局变量 errno 来获取错误代码。
创建客户端实例
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
//创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
//发起连接
struct sockaddr_in server_info; // 存储服务器信息
bzero(&server_info, 0);
server_info.sin_family = AF_INET; //地址族
server_info.sin_port = htons(7000); //绑定端口
server_info.sin_addr.s_addr = inet_addr("192.168.175.129"); //绑定ip地址
if(connect(sockfd, (struct sockaddr*)&server_info, sizeof(server_info)) == -1)
{
perror("connect");
exit(2);
}
//发送信息
char buf[1024] = {0};
while(1)
{
scanf("%s", buf);
if(send(sockfd, buf, sizeof(buf), 0) == -1)
{
perror("send");
exit(3);
}
if(!strcmp(buf, "bye"))
break;
bzero(buf, sizeof(buf));
}
close(sockfd);
return 0;
}
TCP并发服务器
使用多线程来实现响应多个客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *client_recv(void *arg)
{
int fd = *(int *)arg;
char buf[1024] = {0};
ssize_t size;
while(1)
{
size = recv(fd, buf, sizeof(buf), 0);
if(size == -1)
{
perror("recv");
break;
}
else if(size == 0)
break;
if(!strcmp(buf, "bye"))
break;
printf("%s\n", buf);
bzero(buf, 1024);
}
printf("客户端 %d 退出\n", fd);
close(fd);
}
int main()
{
//创建socket,参数说明:
//指定协议族:AF_INET(IPv4)和 AF_INET6(IPv6)
//指定套接字使用的协议,通常为0,表示根据 domain 和 type 自动选择合适的协议。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
//绑定
//sockaddr_in 是一个用于表示 IPv4 地址的结构体
struct sockaddr_in server_info; //用于保存服务器的信息(IP PROT)
bzero(&server_info, sizeof(struct sockaddr_in)); //清空,bzero() 是一个函数,用于将指定内存区域的前几个字节设置为零
server_info.sin_family = AF_INET; //地址族
server_info.sin_port = htons(7000); //端口,大于1024都行
// server_info.sin_addr.s_addr = inet_addr("127.0.0.1"); //表示回环IP,用于测试
server_info.sin_addr.s_addr = inet_addr("192.168.175.129");
if(bind(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1)
{
perror("bind");
exit(2);
}
//设置监听队列
if(listen(sockfd, 10) == -1)
{
perror("listen");
exit(3);
}
printf("等待客户端的连接...\n");
//接受连接
struct sockaddr_in client_info;
int length = sizeof(client_info);
int fd;
while(1)
{
fd = accept(sockfd, (struct sockaddr *)&client_info, &length);
if(-1 == fd)
{
perror("accept");
exit(4);
}
printf("接受客户端的连接 %d\n", fd);
//为每个客户端创建一个线程
pthread_t tid;
if(pthread_create(&tid, NULL, client_recv, &fd) != 0)
{
perror("pthread_create");
exit(4);
}
pthread_detach(tid);
}
// close(fd); //关闭TCP连接,不能在接受数据
close(sockfd); //关闭socket,不能再处理客户端的请求
//socket用于处理客户端的连接,fd用于处理客户端的消息
return 0;
}