流套接字(SOCK_STREAM)
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP协议。
数据报套接字(SOCK_DGRAM)
数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在应用层中做相应的处理。
原始套接字(SOCK_RAW)
原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。
实际应用中,数据报socket的应用场景比较少
socket函数:socket函数用于创建一个新的socket,也就是向系统申请一个socket资源,socket函数用户客户端和服务端。socket()函数的返回值其本质是一个文件描述符,是一个整数。一般情况下,socket创建都会成功,除非系统资源耗尽,socket函数一般不会返回失败(-1)。其函数声明如下:
int socket(int domain, int type, int protocol);
domain
协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址 ,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type
指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式socket(SOCK_STREAM)是一种面向连接的socket,针对于面向连接的TCP服务应用。数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务应用。
protocol
指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
示例代码如下:
//1.创建套接字
int ServerSockFd;
if((ServerSockFd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==-1)//socket创建失败
{
perror("socket 创建失败");
return -1;
}
sockaddr结构体与sockaddr_in结构体:
sockaddr在头文件#include
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
sockaddr_in在头文件#include
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};
两个函数 htons() 和 inet_addr()。
//2.进行绑定,指定用于通信的ip地址和端口
struct sockaddr_in ServerAddr;//服务端地址信息数据结构
//将协议簇指定为ipv4
ServerAddr.sin_family=AF_INET;
//服务端程序绑定本主机的任意IP地址
ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);
服务端程序绑定指定ip
//ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
ServerAddr.sin_port=htons(9000);
bind函数:服务端把用于通信的地址和端口绑定到socket上。如果绑定的地址错误,或端口已被占用,bind函数一定会报错,否则一般不会返回错误。如无错误发生,则bind()返回0。否则的话,将返回-1。其函数声明如下:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if(bind(ServerSockFd,(sockaddr*)&ServerAddr,sizeof(ServerAddr))!=0)
{
perror("bind failed");
close(ServerSockFd);
return -1;
}
listen函数把主动连接socket变为被动连接的socket,使得这个socket可以接受其它socket的连接请求,从而成为一个服务端的socket。//返回:0-成功, -1-失败 listen函数一般不会返回错误。其函数声明如下:
int listen(int sockfd, int backlog);
参数sockfd,已经被bind过的socket。
socket函数返回的socket是一个主动连接的socket,在服务端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被动等待客户端来连接。需要通过某种方式来告诉系统当前的socket是可连接的,则可以通过调用listen函数来完成这件事。
参数backlog,这个参数涉及请求队列的大小。
当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没办法处理的,只能把请求放进缓冲区,等待当前请求结束后,再从缓冲区中读取出来进行处理。如果不断有新的请求进来,按照先后顺序在缓冲区排队,直到缓冲区满,这个缓冲区就称为请求队列。
当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求。
示例代码如下:
//3.将socket设置为监听模式 listen成功返回0,失败返回-1
if(listen(ServerSockFd, 5))
{
perror("listen failed");
close(ServerSockFd);
return -1;
}
accept函数等待客户端的连接,如果没有客户端连上来,它就一直等待,这种方式称之为阻塞。accept等待到客户端的连接后,创建一个新的socket,函数返回值就是这个新的socket,服务端使用这个新的socket和客户端进行报文的收发。accept在等待的过程中,如果被中断或其它的原因,函数返回-1,表示失败,如果失败,可以重新accept。
// 返回值:成功则返回0,失败返回-1,错误原因存于errno 中。
// accept在等待的过程中,如果被中断或其它的原因,函数返回-1,表示失败,如果失败,可以重新accept。
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
示例代码如下:
//4.接收客户端的请求
int ClientSockFd;
struct sockaddr_in ClientAddr;
int len=sizeof(ClientAddr);
if((ClientSockFd=accept(ServerSockFd, (sockaddr*)&ClientAddr, (socklen_t*)&len))==-1)
{
perror("accept failed");
close(ServerSockFd);
close(ClientSockFd);
return -1;
}
send函数用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据。
// 函数返回已发送的字符数。出错时返回-1,错误信息errno被标记
// 如果send函数返回的错误(<=0),表示通信链路已不可用。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
示例代码如下:
if((ret=send(ClientSockFd,sen,sizeof(sen),0))==-1)
{
std::cout<<"send ret="<<ret<<std::endl;
break;
}
std::cout<<"服务端发送:"<<rec<<std::endl;
std::cout<<"send ret="<<ret<<std::endl;
recv函数用于接收对端通过socket发送过来的数据。不论是客户端还是服务端,应用程序都用recv函数接收来自TCP连接的另一端发送过来数据。
// 函数返回已接收的字符数。出错时返回-1,失败时不会设置errno的值。
// 如果recv函数返回的错误(<=0),表示通信通道已不可用。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
示例代码如下:
//recv接收函数,失败返回-1
if((ret=recv(ClientSockFd,rec,sizeof(rec),0))==-1)
{
std::cout<<"recv ret="<<ret<<std::endl;
break;
}
std::cout<<"服务端接收:"<<rec<<std::endl;
std::cout<<"recv ret="<<ret<<std::endl;
connect函数用于将参数sockfd 的socket 连至参数serv_addr 指定的服务端,参数addrlen为sockaddr的结构长度。返回值:成功则返回0,失败返回-1,错误原因存于errno 中。connect函数只用于客户端,,如果服务端的地址错了,或端口错了,或服务端没有启动,connect一定会失败。
int connect(int sockfd, struct sockaddr * serv_addr, int addrlen);
示例代码如下:
//申请服务端地址信息数据结构 因为要与服务端进行相连
struct sockaddr_in ServerAddr;
//绑定端口和地址
ServerAddr.sin_family=AF_INET;//ipv4
ServerAddr.sin_port=htons(9000);
ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//2.连接套接字
if(connect(ClientSockFd, (sockaddr*)&ServerAddr, sizeof(ServerAddr))==-1)
{
perror("connect failed");
return -1;
}
std::cout<<"客户端连接成功"<<std::endl;
最后完整简易服务端代码:https://blog.csdn.net/weixin_45256307/article/details/129417531
最后完整简易客户端代码:https://blog.csdn.net/weixin_45256307/article/details/129417589