🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️本博客致力于知识分享,与更多的人进行学习交流
用于网络应用开发,使用系统提供的一套API函数接口,称为套接字函数
所有的I/O设备都被抽象为文件,一切皆文件,Everything is a File,磁盘、网络数据、终端,甚至进程间通信工具管道pipe等都被当做文件对待。
套接字文件描述符包含有IP地址和PORT端口号。
socket创建套接字
函数原型:int sockfd=socket(AF_INET,SOCKET_STREAM(流式or报式),int protocol(默认流式协议TCP))
成功返回sock_fd,失败返回-1,并设置errno
bind绑定IP和端口
函数原型:bind(sock_fd,struct sockaddr *addr)
struct sockaddr *addr
是早期的网络通信结构体,为了向前兼容,仍然保留。
struct sockaddr_in addr
网络信息结构体
addr.sin_family=AF_INET
addr.sin_port=大端(8080)
addr.sin_addr.s_addr=大端(ip)
大小端转换常用函数
htons()
小端转大端端口
htonl()
小端转大端IP
ntohs()
大端转小端端口
ntohl()
大端转小端IP
inet_pton(AF_INET,char *ip,void *addr)
inet_ntop(AF_INET,void *addr,char *ip,16)
绑定可以对socket设置自定义的IP和端口号,其次当绑定某端口的进程退出,可以临时禁用端口号,禁用时长为2MSL。
成功返回0,失败返回-1
listen网络事件监听(TCP)
函数原型:listen(int sockfd,int backlog)
backlog:监听序列数
成功返回0,错误返回-1,并设置errno,用于错误处理
connet请求TCP连接函数(主动端)
函数原型:connect(int mysockfd,struct sockaddr * dest,socklen_t addrlen)
客户端调用该函数向服务器发起TCP连接请求
成功返回0,错误返回-1,并设置errno,用于错误处理
Accept等待TCP连接请求,完成连接(被动端)
Accept为阻塞等待连接,每次只能连接一个客户端,如果要多次连接,要执行多次该函数
函数原型:
int client_fd=accpet(int server_fd,sockaddr * clientAddr(output),socklen_t *Addrlen(intput|output))
arg[1] 包含自身网络信息的socket
arg[2] 连接成功,传出保存客户端的网络信息(IP,port)
arg[3] 传入可以接收的网络信息大小,传出实际大小
成功返回请求端的socketfd,错误返回-1,并设置errno,用于错误处理
CS架构中,服务器socket只有一个,客户端socket有多个(server_fd用于连接,client_fd用于与客户端交互数据)
recv读取数据
在Linux下,read也可以读取socket数据(TCP),因为在Linux下,套接字以文件描述符的形式记录。
函数原型:ssize_t recv(int sockfd,void *buf,size_t size,int flag)
成功返回读取的数据量,失败返回-1
可以通过将flag设置为MSG_DONTWAIT
实现非阻塞读取数据
send发送数据
在Linux下,write也可以发送socket数据(TCP),因为在Linux下,套接字以文件描述符的形式记录。
函数原型:ssize_t send(int sockfd,void *buf,size_t len,int flag)
面试题:
在编写一个服务端-客户端模型时,客户端异常退出,服务端也异常退出。
原因
当客户端异常退出时,客户端关闭了它的读端文件描述符。此时,如果服务端尝试向管道或套接字写数据,会触发 SIGPIPE
信号,默认行为是终止进程,因此服务端也会异常退出。
解决方案
可以在 send()
函数中使用 MSG_NOSIGNAL
标志位,来忽略 SIGPIPE
信号,从而避免服务端异常退出
send(int sockfd, buffer, len, MSG_NOSIGNAL);
使用 MSG_NOSIGNAL
标志位,写操作在对端关闭连接的情况下不会触发 SIGPIPE
信号,而是会返回 EPIPE
错误,程序可以根据这个错误码进行处理而不是直接终止。
在公网上利用TCP协议实现网络数据传输
由于服务端和客户端不在同一个局域网下,所以需要通过互联网进行通信,虚拟机要配置网络连接。
服务端:
服务端是运行在公网上的阿里云服务器。
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#define _SERVER_IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
#define _BACKLOG 128
#define _SHUTDOWN 1
#define _TRUE 1
#define _FALSE 0
#define _IPSIZE 16
#define _RECVLEN 1500
int main()
{
struct sockaddr_in serverAddr,clientAddr;
int server_fd;
int client_fd;
char Result[_RECVLEN];
char client_ip[_IPSIZE];
socklen_t Addrlen;
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(_PORT);
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
server_fd=socket(AF_INET,SOCK_STREAM,0);
bind(server_fd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
listen(server_fd,_BACKLOG);
printf("Test TCP Server Version 1.1.0 is Running...\n");
while(_SHUTDOWN)
{
Addrlen=sizeof(clientAddr);
if((client_fd=accept(server_fd,(struct sockaddr*)&clientAddr,&Addrlen))>0)
{
bzero(Result,sizeof(Result));
bzero(client_ip,sizeof(client_ip));
inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,client_ip,_IPSIZE);
printf("Connection From :IP[%s],PORT[%d]\n",client_ip,ntohs(clientAddr.sin_port));
sprintf(Result,"Hi [%s] Welcome to my TCP test server!service version 1.1.0...",client_ip);
send(client_fd,Result,strlen(Result),0);
close(client_fd);
}
else
{
perror("accpet failed");
close(server_fd);
exit(0);
}
}
close(server_fd);
return 0;
}
客户端:
客户端是运行在本地上的虚拟机镜像
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//客户端源码编写,连接服务器成功,服务器反馈信息
#define _IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
int main()
{
struct sockaddr_in ServerAddr;
bzero(&ServerAddr,sizeof(ServerAddr));
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(_PORT);
inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
int Myfd=socket(AF_INET,SOCK_STREAM,0);
//看需求决定是否要绑定
char Response[1024];//存放服务端反馈信息
ssize_t recvlen;
bzero(Response,sizeof(Response));
if((connect(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
{
if((recvlen=recv(Myfd,Response,sizeof(Response),0))>0)
{
printf("%s\n",Response);
}
}
else
{
printf("Connect failed\n");
close(Myfd);
exit(0);
}
close(Myfd);
printf("Client is Over\n");
return 0;
}
运行结果:
函数二次包裹
在我们编写代码时,通常需要根据函数的返回值来判断函数调用情况,这个过程需要大量的if else语句,减少了代码阅读的简洁性。所以为了方便我们阅读,需要对函数进行二次包裹。
什么是二次包裹?
在自定义的头文件中重新声明一个函数,函数名与接口函数不同,返回值、参数均与接口函数相同。在相应的源文件中定义我们的函数,将逻辑判断的过程放在函数中。这样在调用函数时就省去大量的if else判断
#ifndef __MySock_H__
#define __MySock_H__
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int SOCKET(int domain,int type,int protocol);
int BIND(int sockfd,struct sockaddr* addr,socklen_t addrlen);
ssize_t RECV(int sockfd,void *buf,size_t len,int flags);
ssize_t SEND(int sockfd,void *buf,size_t len,int flags);
int CONNECT(int sockfd,struct sockaddr *addr,socklen_t addrlen);
int ACCEPT(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
int LISTEN(int sockfd,int backlog);
#endif
#include "MySock.h"
int SOCKET(int domain,int type,int protocol)
{
int reval=socket(domain,type,protocol);
if(reval==-1)
{
perror("socket call failed");
exit(0);
}
return reval;
}
int BIND(int sockfd,struct sockaddr* addr,socklen_t addrlen)
{
int reval=bind(sockfd,addr,addrlen);
if(reval==-1)
{
perror("bind call failed");
exit(0);
}
return reval;
}
ssize_t RECV(int sockfd,void *buf,size_t len,int flags)
{
ssize_t reval;
reval=recv(sockfd,buf,len,flags);
if(reval==0)
perror("recv call failed");
return reval;
}
ssize_t SEND(int sockfd,void *buf,size_t len,int flags)
{
ssize_t reval;
reval=send(sockfd,buf,len,flags);
if(reval==-1)
perror("send call failed");
return reval;
}
int CONNECT(int sockfd,struct sockaddr *addr,socklen_t addrlen)
{
int reval=connect(sockfd,addr,addrlen);
if(reval==-1)
{
perror("connect call failed");
exit(0);
}
return reval;
}
int ACCEPT(int sockfd,struct sockaddr* addr,socklen_t *addrlen)
{
int reval=accept(sockfd,addr,addrlen);
if(reval==-1)
{
perror("accept call failed");
exit(0);
}
return reval;
}
int LISTEN(int sockfd,int backlog)
{
int reval=listen(sockfd,backlog);
if(reval==-1)
{
perror("listen call failed");
exit(0);
}
return reval;
}
使用包裹后的函数对我们TCP通信的客户端进行改写:
代码看起来简洁多了
#include "MySock.h"
//客户端源码编写,连接服务器成功,服务器反馈信息
#define _IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
int main()
{
struct sockaddr_in ServerAddr;
bzero(&ServerAddr,sizeof(ServerAddr));
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(_PORT);
inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);
//看需求决定是否要绑定
char Response[1024];//存放服务端反馈信息
ssize_t recvlen;
bzero(Response,sizeof(Response));
if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
{
if((recvlen=recv(Myfd,Response,sizeof(Response),0))>0)
{
printf("%s\n",Response);
}
}
close(Myfd);
printf("Client is Over\n");
return 0;
}