4、网络编程——TCP相关的API及其实现的步骤
目录
一、API
1、listen(连接套接字)
2、accept(等待连接)
注意
3、send(向TCP套接字发送数据)
4、recv(从TCP套接字收取数据)
5、connect(连接对端监听套接字)
二、使用步骤
1、服务端
2、客户端
三、代码
1、服务器(接收端)
2、客户端(发送方)
一、API
1、listen(连接套接字)
将带连接套接字设置为监听套接字,并设置最大同时接收连接请求个数
int listen(int sockfd, int backlog);
参数:
sockfd:待连接套接字
backlog:最大同时接收连接请求个数
返回值:
成功:0,并将 sockfd 设置为监听套接字
失败:-1
由于历史原因,各种系统对 backlog 的理解并不一致,以 LINUX 为例,监听端能同时接收的 最大连接请求个数为 backlog+4
2、accept(等待连接)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:监听套接字
addr:通用地址结构体,用以存储对端地址(IP+PORT)
addrlen:参数 addr 的存储区域大小
返回值:
成功:已连接套接字(非负整数)
失败:-1
注意
该函数是用来等待客户端连接的, 如果有新的客户端连接上来那么该函数会返回一个新的sockfd 作为该客户端的连接套接字。
如果把accept 写在循环体内部, 该函数会造成阻塞, 同时如果有多个客户端连接那么该函数会返 回这些客户所对应的多个已连接套接字。
3、send(向TCP套接字发送数据)
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:即将被发送的数据
len:数据长度
flags:发送标志。
MSG_NOSIGNAL:当对端已关闭时,不产生 SIGPIPE 信号
MSG_OOB:发送紧急(带外)数据,只针对 TCP 连接
返回值:
成功:已发送字节数
失败:-1
当 flags 为 0 时,send 与 write 作用相同。
4、recv(从TCP套接字收取数据)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:存储数据缓冲区
len:缓冲区大小
flags:接收标志
MSG_OOB:接收紧急(带外)数据
返回值:
成功:已接收字节数
失败:-1
注意:当 flags 为 0 时,recv 与 read 作用相同。
5、connect(连接对端监听套接字)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:待连接套接字
addr:包含对端地址(IP+PORT)的通用地址结构体的指针
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
二、使用步骤
1、服务端
(1)创建实现通信的工具(socket)
(2)设置IP+端口号
(3)绑定端口号等信息(bind)
(4)设置监听(listen)
(5)等待客户端连接(accept)
(6)互相聊天(send、recv)
(7)关闭聊天(套接字)
2、客户端
(1)创建实现通信的工具(socket)
(2)设置需要连接的服务区的IP地址以及端口号
(3)尝试连接服务器(connect)
(4)相互聊天(send、recv)
(5)关闭聊天(套接字)
三、代码
1、服务器(接收端)
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
int main(int argc, char const *argv[])
{
// 创建一个套接字 (购买一台手机)
//IPV4、流式套接字
int sock_fd = socket( AF_INET , SOCK_STREAM , 0 ); // 使用IPV4协议簇, 流式套接字(TCP)
if (-1 == sock_fd)
{
perror("socket rror");
return -1 ;
}
// 配置自己的IP和端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
int addrlen = sizeof(struct sockaddr_in);
struct sockaddr_in my_addr = {
.sin_addr.s_addr = htonl(INADDR_ANY) , // 设置服务器的地址, INADDR_ANY 指本机中任何一个地址
.sin_family = AF_INET , // 使用 IPV4 协议簇
.sin_port = htons(65000) // 设置服务器的端口号为 65000
};
// 绑定地址信息 (IP + 端口号 + 协议簇)
int ret_val = bind( sock_fd, (struct sockaddr *)&my_addr, addrlen);
if (ret_val == -1 )
{
perror("bind error");
return -1 ;
}else{
printf(" bind succeed !!\n") ;
}
// 设置监听 (打开铃声)
if(listen( sock_fd, 5 )) // 设置sock_fd 为监听套接字, 同时可以接收连接请求数为 5 + 4(默认值)
{
perror("listen error");
return -1 ;
}
// 等待连接
struct sockaddr_in from_addr;
int connect_fd = accept( sock_fd , (struct sockaddr *)&from_addr, &addrlen);
if (connect_fd == -1 )
{
perror("connect error");
return -1 ;
}
else{
printf("connect succeed !!\n");
}
// 聊天
char * msg = calloc(128,1);
while(1)
{
bzero(msg , 128 );
ret_val = recv(connect_fd , msg , 128 , 0 );
if ( ret_val != -1 )
{
printf("recv msg : %s " , msg );
}
else{
perror("recv error !!");
}
}
// 关闭套接字
close(sock_fd);
return 0;
}
2、客户端(发送方)
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
int main(int argc, char const *argv[])
{
// 创建一个套接字 (购买一台手机)
int sock_fd = socket( AF_INET , SOCK_STREAM , 0 ); // 使用IPV4协议簇, 流式套接字(TCP)
if (-1 == sock_fd)
{
perror("socket rror");
return -1 ;
}
// 配置自己的IP和端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
int addrlen = sizeof(struct sockaddr_in);
struct sockaddr_in server_addr = {
.sin_addr.s_addr = inet_addr("192.168.102.2") , // 设置服务器的地址
.sin_family = AF_INET , // 使用 IPV4 协议簇
.sin_port = htons(65000) // 设置服务器的端口号为 65000
};
// 连接服务器
if( connect(sock_fd , (const struct sockaddr *)&server_addr, addrlen))
{
perror("connect error");
return -1 ;
}
// 聊天
char * msg = calloc(128,1);
while(1)
{
bzero(msg , 128 );
fgets(msg , 128 , stdin);
int ret_val = send(sock_fd , msg , strlen(msg) , 0 );
if ( ret_val != -1 )
{
printf("send msg succeed : %d byte \n" , ret_val );
}
else{
perror("send error !!");
}
}
// 关闭套接字
close(sock_fd);
return 0;
}
上述程序缺点
若客户端突然掉线,服务器奔溃(需要三次握手)