涉及到的一些函数:
socket
int socket(int domain, int type, int protocol);
//用于创建套接字,它是网络编程中的基础函数,用于建立网络通信。套接字是网络通信的端点,允许应用程序通过网络发送和接收数据。
//domain:表示套接字的地址族(Address Family)。常用的有 AF_INET(IPv4 地址族)和 AF_INET6(IPv6 地址族)。
//type:表示套接字的类型,常用的有 SOCK_STREAM(流套接字,用于TCP通信)和 SOCK_DGRAM(数据报套接字,用于UDP通信)等。
//protocol:表示使用的协议。通常设置为0,表示根据 domain 和 type 自动选择合适的协议。
socket函数返回一个整数值,如果创建套接字成功,它会返回一个非负的套接字描述符,用于后续的套接字操作。如果创建失败,返回 -1,表示出现了错误。
sockaddr_in
用于指定服务器地址、客户端地址
struct sockaddr_in {
short sin_family; // 地址族,通常设置为 AF_INET
unsigned short sin_port; // 端口号,需要使用 htons 函数转换为网络字节序
struct in_addr sin_addr; // IP地址,使用结构体 in_addr 表示
char sin_zero[8]; // 保留字段,一般设置为 0
};
htons
在网络编程中,需要将数据在不同计算机之间进行传输,因此必须保证数据的字节序一致性。这就是 htons 函数的作用:将一个16位整数从主机字节序转换为网络字节序。具体来说,它将主机字节序的16位整数的字节顺序进行调整,以符合网络字节序的要求。
inet_addr
in_addr_t inet_addr(const char *cp);//用于将点分十进制字符串表示的IPv4地址转换为网络字节序的32位二进制整数表示的函数。
//cp: 是一个指向包含点分十进制字符串的指针,表示要转换的IPv4地址
bind
用于将一个套接字(socket)绑定到一个特定的地址和端口上,以便在这个地址和端口上监听传入的连接请求。在网络编程中,通常用于服务器端创建一个监听套接字,以便客户端能够连接到服务器。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd:要绑定的套接字的文件描述符。
//addr:一个指向 struct sockaddr 结构体的指针,表示要绑定的地址信息。 //addrlen:addr 结构体的长度。
listen
用于将一个已绑定的套接字设置为监听模式,以便可以接受客户端的连接请求。它告诉操作系统,该套接字将用于接受传入的连接,而不是主动发起连接。
int listen(int sockfd, int backlog);
//sockfd:要监听的套接字的文件描述符,通常是一个已经通过 bind 绑定了地址和端口的套接字。
//backlog:定义了内核允许在套接字队列中排队等待的最大连接数。这个参数通常设置为一个合适的值,通常在 5 到 128 之间。
accept
用于接受传入的连接请求,并创建一个新的套接字来处理与客户端的通信。它通常在服务器中的主循环中使用,以等待客户端连接。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd:表示已经通过 listen 函数设置为监听模式的套接字。
//addr:一个指向 struct sockaddr 结构体的指针,用于存储客户端的地址信息。 //addrlen:一个指向 socklen_t 类型的指针,用于指定 addr 结构体的大小。
accept 函数将会阻塞,直到有一个客户端连接请求到达为止。一旦连接请求到达,accept 函数将返回一个新的套接字文件描述符,该套接字表示与客户端的连接。同时,它会填充 addr 结构体以存储客户端的地址信息。
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);//用于将数据从一个已连接的套接字发送到另一个套接字。通常用于在客户端和服务器之间传输数据。
//sockfd:指定发送数据的套接字文件描述符。
//buf:一个指向要发送数据的缓冲区的指针。
//len:要发送的数据的长度,以字节为单位。
//flags:可选参数,通常可以设置为 0。
send 函数返回已发送的字节数,如果发生错误则返回 -1。在网络编程中,通常需要多次调用 send 函数来确保发送完整的数据。此函数通常是阻塞的,直到数据成功发送或发生错误。
connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//函数用于建立与远程主机的连接,通常在客户端程序中使用。
//sockfd:套接字文件描述符,指定要连接的本地套接字。
//addr:一个指向 sockaddr 结构体的指针,描述了远程服务器的地址和端口信息。
//addrlen:addr 结构体的大小。
连接成功,返回 0;如果出现错误,返回 -1。
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);//用于从套接字接收数据
//sockfd:套接字文件描述符,指定从哪个套接字接收数据。 //buf:一个指向数据缓冲区的指针,接收到的数据将存储在这里。 //len:接收缓冲区的最大长度。 //flags:控制接收操作的标志位,通常设置为 0。
recv 函数的返回值是接收到的数据的字节数,如果出现错误,返回 -1。此函数可以阻塞等待数据,直到有数据到达,如果返回0,说明客户端关闭了。
服务器端代码(一对一)
监听指定端口(6000),并接受来自客户端的连接请求。一旦有客户端连接成功,它会接收客户端发送的数据,打印接收到的数据,并向客户端发送 "ok",然后关闭连接。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket creation failed");
exit(1);
}
struct sockaddr_in saddr, caddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (res == -1)
{
perror("bind failed");
exit(1);
}
res = listen(sockfd, 5);
if (res == -1)
{
perror("listen failed");
exit(1);
}
while (1)
{
int len = sizeof(caddr);
int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
if (c < 0)
{
perror("accept failed");
continue;
}
printf("accept c=%d\n", c);
char buff[128] = {0};
int n;
while ((n = recv(c, buff, sizeof(buff) - 1, 0)) > 0)
{
buff[n] = '\0';
printf("Received: %s\n", buff);
int sent_bytes = send(c, "ok", 2, 0);
if (sent_bytes < 0)
{
perror("send failed");
break;
}
if (strcmp(buff, "end\n") == 0)
{
close(c);
close(sockfd);
exit(0);
}
}
close(c);
}
close(sockfd);
return 0;
}
服务器端代码(多对一)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
void * fun(void* arg)
{
int *p = (int*)arg;
int c = *p;
free(p);
while( 1 )
{
char buff[128] = {0};
int n = recv(c,buff,127,0);
if ( n <= 0)// recv返回值n == 0 说明客户但关闭连接了
{
break;
}
printf("recv=%s\n",buff);
send(c,"ok",2,0);
}
close(c);//关闭连接 挥手
printf("client close\n");
}
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);//套接字 文件描述符
if ( sockfd == -1 )
{
exit(1);
}
struct sockaddr_in saddr,caddr;//套接字地址 ip port
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if ( res == -1 )
{
printf("bind err\n");
exit(1);
}
res = listen(sockfd,5);
if ( res == -1 )
{
exit(1);
}
while( 1 )
{
int c = accept(sockfd,NULL,NULL);
if ( c < 0 )
{
continue;
}
printf("accept c=%d\n",c);
pthread_t id;
int * p = (int*)malloc(sizeof(c));
*p = c;
pthread_create(&id,NULL,fun,(void*)p);
}
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("Socket creation failed");
exit(1);
}
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));//connect表示三次握手开始
if (res == -1)
{
perror("Connect error");
exit(1);
}
char buff[128] = {0};
printf("Input (type 'end' to exit):\n");
while (1)
{
fgets(buff, 128, stdin);
if (strcmp(buff, "end\n") == 0)
{
break;
}
send(sockfd, buff, strlen(buff) - 1, 0);
memset(buff, 0, 128);
recv(sockfd, buff, 127, 0);
printf("Received: %s\n", buff);
}
close(sockfd);
exit(0);
}
本文详细介绍了网络编程中的套接字功能,包括socket函数、地址族、套接字类型、协议转换、sockaddr_in结构、htons函数、inet_addr函数以及bind、listen、accept、send和connect等关键操作,展示了服务器端一对一和多对一的示例代码。





