TCP的套接字的socket通信
本篇主要通过实现TCP的套接字通信例程介绍套接字基础知识。
一、概念
套接字:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点,就叫做套接字(socket)或插口。套接字用(IP地址:端口号)表示。
生成套接字的三个主要参数:通信的目的IP地址,使用传输层协议(TCP或UDP)和使用的端口。socket原意为插座,通过这三个参数与另一个插座(socket)绑定,应用层和传输层就可以通过套接字接口,区分来自不同应用程序或网络通信,实现数据传输的并发服务。
任务与任务之间的通信节点。要通过套接字进行通信,至少需要一对套接字,一个运行于客户机端,称之为ClinetSocket,另一个运行于服务器端,称之为ServerSocket。
简单的来说,我们需要套接字这个桥梁来实现客户端和服务器端的通信。
二、TCP套接字通信流程
服务器端:
1.创建套接字(socket)
2.将套接字绑定到本地地址和端口上(bind)
3.将套接字的状态设置为监听状态(listen)
4.接受连接请求,并且得到用于通信的套接字(accept)
5.使用接受连接请求后得到的套接字进行通信(send/recv)
6.通信完毕,释放套接字(close)
客户端:
1.创建套接字(socket)
2.向服务器端发起连接请求(connect)
3.连接成功后,开始通信操作(send/recv)
在这里出现的红色字体则是我们需要使用的函数。
三、相关实现函数
1.socket函数
函数原型 | 函数介绍 |
int socket(int domain, int type, int protocol); | 创建一个套接字,成功返回一个套接字,失败返回-1 |
domain:域名,man手册上有具体介绍,这里我们是基于TCP进行通信,所以选择AF_INET | |
type:表示套接字类型,选择流式套接字,SOCK_STREAM | |
protocol:套接口所用协议 |
2.bind函数
函数原型 | 函数介绍 |
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); | 将套接字绑定到指定端口和地址上,成功返回0,失败返回-1 |
sockfd:套接字 | |
addr:关于绑定端口的具体信息 | |
addrlen:addr的长度 |
可在 /usr/include/netinet/in.h文件中查到该结构体的介绍
struct sockaddr_in
{
__uint8_t sin_len; //表示该结构体的长度
sa_family_t sin_family; //地址家族,也就是域名
in_port_t sin_port; //端口号
struct in_addr sin_addr; //地址号
char sin_zero[8];
}
3.listen函数
函数原型 | 函数介绍 | |
int listen(int sockfd, int backlog); | 将套接字设置为监听状态,成功返回0,失败返回-1 | |
sockfd:套接字 | ||
backlog:表示等待连接的最大队列长度。代表该端口同时最多可连接多少个客户端,设置为5,则最多同时连接5个客户端 |
4.accept函数
函数原型 | 函数介绍 |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); | 接受客户端的连接请求,成功返回一个新的套接字,失败返回-1 |
sockfd:套接字 | |
addr:接受连接端的地址信息 | |
addrlen:addr结构体指针的长度 | |
若addr 和 addrlen都选择0的话,则代表选择了套接字绑定的端口 |
5.close函数
函数原型 | 函数介绍 |
int close(int fd); | 关闭套接字 |
sockfd:套接字 |
6.connect函数
函数原型 | 函数介绍 |
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); | 通过套接字发送数据,成功返回0,失败返回-1 |
sockfd:套接字 | |
addr:连接服务端的地址信息 | |
addrlen:addr结构体指针的长度 |
7.send函数
函数原型 | 函数介绍 |
ssize_t send(int sockfd, const void *buf, size_t len, int flags); | 往套接字中发送数据,成功返回发送的数据个数,失败返回-1 |
sockfd:套接字 | |
buf:被发送数据的缓冲区 | |
len:发送数据的长度 | |
flags:函数的调用方式,一般填0,具体信息查看man手册 |
8.recv函数
函数原型 | 函数介绍 |
ssize_t recv(int sockfd, void *buf, size_t len, int flags); | 从套接字中接收数据,成功返回接收到的数据个数,失败返回-1 |
sockfd:套接字 | |
buf:接收数据的缓冲区 | |
len:接收到数据的长度 | |
flags:函数的调用方式,一般填0,具体信息查看man手册 |
四、具体实现代码
服务端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int sockfd = 0;
int acceptfd = 0;
struct sockaddr_in server_addr; //结构体定义在 netinetin.h 当中
char str[200] = {'\0'};
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //Socket operation on non-socket
{
perror("套接字创建失败");
return -1;
}
printf("套接字创建成功;\n");
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("套接字绑定失败 ");
return -1;
}
printf("套接字绑定成功\n");
if (listen(sockfd, 5) < 0)
{
perror("套接字监听失败 ");
return -1;
}
printf("套接字监听成功\n");
if ((acceptfd = accept(sockfd, 0, 0)) < 0)
{
printf("连接到发送端;\n");
}
while(1)
{
if (recv(acceptfd, str, 200, 0) < 0)
{
perror("fail to recv ");
close(acceptfd);
return -1;
}
else
{
printf("接收到数据: %s;\n", str);
if (send(acceptfd, str, 200, 0) < 0)
{
printf("数据发送失败;\n");
}
else
{
printf("数据发送成功: %s \n", str);
}
}
}
return 0;
}
用户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int sockfd = 0;
int acceptfd = 0;
struct sockaddr_in server_addr; //结构体定义在 netinetin.h 当中
char str[200] = {'\0'};
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //Socket operation on non-socket
{
perror("套接字创建失败");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
printf("套接字创建成功;\n");
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("连接失败 ");
return -1;
}
printf("连接到接收端\n");
while(1)
{
printf("请输入需要发送的数据: ");
scanf("%s", str);
send(sockfd, str, 200, 0);
if (recv(sockfd, str, 200, 0) < 0 )
{
printf("接收数据错误;\n");
}
else
{
printf("接收到数据:%s ;\n", str);
}
}
return 0;
}
在程序中的注释中有一句,Socket operation on non-socket,这个是没注意赋值运算和比较运算导致编译程序报错的错误信息,假如成程序改成
if (sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) //Socket operation on non-socket
此时编译程序则会报错Socket operation on non-socket
在运行程序之前,我们可以使用:netstat -tnlp命令获取本地地址。
此处的127.0.0.1 即是地址号。
下面运行程序,我们看看结果。
在这里我们需要注意的是,我们需要先运行服务端的程序,在上面的图片我们能清楚的看到用户端和服务端连接成功了。
在这里,我们则可以看到,客户端发送的数据,服务端能正常接收并且返回,服务端发送的数据,接收端也能正常返回。
程序里面还有个别函数没介绍,这里我们简单的说一下,
atoi(),将字符串转换成整型数
htons()作用是将端口号有主机字节序转换为网络字节序的整数值。(host to net)
inet_addr()将点分十进制的字符串转换为网络地址。得到一个二进制数据。
仓促成文,不当之处,尚祈方家和读者批评指正。联系邮箱1772348223@qq.com