网络通信之套接字
套接字通信原理
socket函数介绍
int socket(int domain, int type, int protocol);
功能:为通信创建一个端点,并返回该端点的文件描述符
参数1:通信域
Name Purpose Man page AF_UNIX, AF_LOCAL 本地通信,同一主机之间进程通信 详情请看man 7 unix AF_INET IPv4 提供的网络通信 详情请看man 7 ip AF_INET6 IPv6 提供的网络通信 详情请看man 7 ipv6
参数2:指定通信语义,可以由多个宏值使用位或连接
SOCK_STREAM:表示提供TCP协议的传输方式
SOCK_DGRAM:表示提供UDP协议的传输方式
SOCK_NONBLOCK:套接字设置非阻塞属性
参数3:如果参数2中仅仅指定一个协议,那么参数3可以填0,如果指定多个,则参数3需要指定特定的协议
TCP协议名称:IPPROTO_TCP
UDP协议名称:IPPROTO_UDP
返回值:成功返回创建的套接字文件描述符,失败返回 -1并置位错误码
TCP实现网络通信
TCP网络通信原理图
TCP相关函数介绍
1> bind绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:位套接字分配名称
参数1:通过socket函数创建出来的套接字文件描述符
参数2:通用地址信息结构体,需要根据具体使用的地址族而定, struct sockaddr仅仅只是为了类型的强制转换,防止出现警告
跨主机间通信:man 7 ip
struct sockaddr_in {
sa_family_t sin_family; /* 表示通信域 */
in_port_t sin_port; /* 端口号的网络字节序 */
struct in_addr sin_addr; /* ip地址 */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* IP地址的网络字节序 */
};
同一主机间通信:man 7 uninx
struct sockaddr_un {
sa_family_t sun_family; /* 表示通信域:AF_UNIX */
char sun_path[108]; /* 套接字文件的地址 */
};
参数3:参数2的大小
返回值:成功返回0,失败返回-1并置位错误码
注意关于bind的两个错误:
1、 Cannot assign requested address:表示IP地址填写错误,检查IP是否有问题
2、Address already in use:表示地址信息正在占用,可以调用函数快速重用,也可以等一会
2> listen监听
int listen(int sockfd, int backlog);
功能:将套接字设置成被动监听状态,已接受客户端的连接请求
参数1:套接字文件描述符
参数2:容纳连接的队列的最大长度,一般填128
返回值:成功返回0,失败返回-1并置为错误码
3> accept接收连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:用于阻塞接收客户端连接请求
参数1:服务器套接字文件描述符
参数2:用于接收对端地址信息结构体的指针
参数3:接收对端地址信息的长度
返回值:成功返回一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码
4> recv接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:从套接字中读取数据到buf中
参数1:用于通信的套接字文件描述符
参数2:接收数据后的容器地址
参数3:接收的数据的大小
参数4:是否阻塞接收 0:表示阻塞接收消息 MSG_DONTWAIT:表示非阻塞接收数据
返回值: >0:表示成功读取的字符个数
=0:表示通信对端已经下线
=-1:表示出错,置位错误码
5> send发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:向通信套接字文件描述符中写入数据
参数1:通信的套接字文件描述符
参数2:要发送数据的起始地址
参数3:要发送数据的大小
参数4:是否阻塞接收 0:表示阻塞接收消息 MSG_DONTWAIT:表示非阻塞接收数据
返回值:成功返回发送字符的个数,失败返回-1并置位错误码
6> connect连接函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字文件描述符连接到addr指向的地址空间中
参数1:客户端套接字文件描述符
参数2:对端地址信息结构体
参数3:参数2的大小
返回值:成功返回0,失败返回-1并置位错误码
TCP服务器端代码实现
#include<myhead.h>
#define SER_PORT 6666 //服务器端口号
#define SER_IP "192.168.119.143"
int main(int argc, const char *argv[])
{
//创建套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
//参数1:标示ipv4的网络通信
//参数2:表示使用TCP通信方式
//参数3:表示默认使用一个协议
if (sfd == -1)
{
perror("socket errror");
return -1;
}
printf("socket success ,sfd = %d\n",sfd);
//将端口号快速重用
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2.为套接字绑定ip地址和端口号
//2.1填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons (SER_PORT);//端口号
sin.sin_addr.s_addr = inet_addr(SER_IP);//ip地址
//2.2绑定工作
if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3将套接字设置成被动监听
if(listen(sfd,128)==-1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4阻塞等待客户端的连接请求
//4.1定义变量用于接收客户端的信息
struct sockaddr_in cin ;//用于接收地址信息
socklen_t addrlen = sizeof(cin);//用于接收长度
int newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd==-1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]已经成功连接一个客户端\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
//5.数据收发
char buf[128] = "";
while(1)
{
//清空缓冲区
bzero(buf,sizeof(buf));
//从客户端套接字中读取数据
int res = recv(newfd,buf,sizeof(buf),0);
if(res == -1)
{
perror("read error");
return -1;
}else if(res == 0)
{
printf("客户端以下线\n");
close(newfd);//关闭客户端套接字
break;
}
//正常接收客户端发来的消息
printf("[%s:%d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
strcat(buf,"*-*");
//将消息回复给客户端
if(send(newfd,buf,strlen(buf),0)==-1)
{
perror("write error");
return -1;
}
printf("发送成功\n");
}
close(newfd);
return 0;
}
TCP客户端实现
#include<myhead.h>
#define SER_PORT 6666 //与服务器一致
#define SER_IP "192.168.119.143" //服务器ip地址
#define CLI_PORT 8888//客户端端口号
#define CLI_IP "192.168.119.143"//客户端ip地址
int main(int argc, const char *argv[])
{
//1.创建用于通信的套接字文件描述符
int cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd == -1)
{
perror("socket error");
return -1;
}
printf("cfd = %d\n",cfd);
//2.绑定IP地址和端口号
//2.1填充地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET; //通信域
cin.sin_port = htons (CLI_PORT);//端口号
cin.sin_addr.s_addr = inet_addr(CLI_IP);//ip地址
//2.2绑定工作
if(bind(cfd,(struct sockaddr *)&cin,sizeof(cin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3.连接到服务器
//3.1填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;//通信域
sin.sin_port = htons(SER_PORT);//服务器端口号
sin.sin_addr.s_addr = inet_addr(SER_IP);//服务器ip地址
//3.2连接到fwq
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("connect error");
return -1;
}
printf("连接服务器成功\n");
//4.数据收发
char buf[128] = "";
while(1)
{
printf("请输入>>>");
fgets(buf,sizeof(buf),stdin);//从终端获取字符串
buf[strlen(buf)-1]=0;
//将数据发送给服务器
send(cfd,buf,strlen(buf),0);
printf("发送成功\n");
if(strcmp(buf,"quit")==0)
//接收服务器传来的数据
//清空容器
bzero(buf,sizeof(buf));
recv(cfd,buf,sizeof(buf),0);
printf("收到服务器消息为:%s\n",buf);
}
//5.关闭套接字
close(cfd);
return 0;
}
TCP服务器通信模型
1、sfd = socket(); //创建一个用于连接的套接字文件描述符
2、bind(); //为服务器套接字绑定ip地址和端口号,为了让客户端额能够找到服务器
3、listen(); //将服务器套接字设置成被动监听状态,用于接收客户端的连接请求
4、newfd = accept(); //阻塞等待客户端的连接请求,如果有客户端发来连接请求,创建一个 新的用于通信的套接字文件描述符
5、while(1) {
send\recv\read\write; //数据收发工作
}
6、close(); //关闭套接字、关闭监听
UDP实现网络通信
UDP网络通信模型
UDP相关函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
//功能:从套接字文件描述符中读取数据,并将对端地址信息结构体接收
参数1:套接字文件描述符
参数2:要接收数据的起始地址
参数3:要接收的数据大小
参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞
参数5:接收对端地址信息结构体
参数6:参数5的大小
返回值:成功返回读取的字节的大小,失败返回-1并置位错误码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
//功能:向套接字文件描述符中读取数据,写给指定的对端接收
参数1:套接字文件描述符
参数2:要发送数据的起始地址
参数3:要发送的数据大小
参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞
参数5:接收对端地址信息结构体
参数6:参数5的大小 返回值:成功返回发送的字节的大小,失败返回-1并置位错误码
UDP服务器端实现
#include<myhead.h>
#define SER_PORT 9999
#define SER_IP "192.168.119.143"
int main(int argc, const char *argv[])
{
//1,创建用于通讯的服务器套接字文件描述符
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd == -1)
{
perror("scoket error");
return -1;
}
printf("sfd = %d\n",sfd);
//2.绑定IP地址和端口号
//2.1填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;//通讯域
sin.sin_port = htons(SER_PORT);//端口号
sin.sin_addr.s_addr = inet_addr(SER_IP);//ip地址
//2.2绑定工作
if(bind(sfd , (struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3.数据收发
char buf[128]="";
struct sockaddr_in cin;//接收对端地址信息
socklen_t addrlen = sizeof(cin);//接收地址长度
while(1)
{
//清空容器
bzero(buf,sizeof(buf));
//从套接字中读取数据
recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen);
if(strcmp(buf,"quit")==0)
{
break;
}
printf("收到消息为:%s\n",buf);
strcat(buf,"*-*");
if(sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,sizeof(cin))== -1)
{
perror("write error");
return -1;
}
printf("发送成功\n");
}
//4.关闭文件描述符
close(sfd);
return 0;
}
UDP客户端实现
#include<myhead.h>
#define SER_PORT 9999
#define SER_IP "192.168.119.143"
#define CLI_PORT 5555
#define CLI_IP "192.168.119.143"
int main(int argc, const char *argv[])
{
//1,创建用于通讯的服务器套接字文件描述符
int cfd = socket(AF_INET,SOCK_DGRAM,0);
if(cfd == -1)
{
perror("scoket error");
return -1;
}
printf("cfd = %d\n",cfd);
//2.绑定IP地址和端口号
//2.1填充地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET;//通讯域
cin.sin_port = htons(CLI_PORT);//端口号
cin.sin_addr.s_addr = inet_addr(CLI_IP);//ip地址
//2.2绑定工作
if(bind(cfd , (struct sockaddr*)&cin,sizeof(cin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3.数据收发
char buf[128]="";
struct sockaddr_in sin;//接收对端地址信息
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
while(1)
{
//从终端获取数据
printf("请输入>>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//将数据发生给服务器
sendto(cfd,buf,strlen(buf),0,(struct sockaddr*)&sin,sizeof(sin));
if(strcmp(buf,"quit")==0)
{
break;
}
//清空容器
bzero(buf,sizeof(buf));
//接收服务器发来的消息
recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL);
printf("接收到服务器消息为:%s\n",buf);
}
//4.关闭文件描述符
close(cfd);
return 0;
}