文章目录
socket 客户端
1. 创建socket
socket()函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
// 实例: IPv4 与字节流的默认的协议,一般为 TCP
int sockfd=socket(AF_INET,SOCK_STREAM,0);
-
domain:一个地址描述。支持AF_INET格式,也就是说ARPA Internet地址格式。
- AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
- AF_INET6 与上面类似,不过是来用IPv6的地址
- AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
-
type:指定socket类型。新套接口的类型描述类型,如:
-
TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。
-
常用的socket类型还有SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
-
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。
-
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP。
-
-
protocol:就是指定协议。套接口所用的协议。如调用者不想指定,可用0。常用的协议有:
- IPPROTO_TCP(TCP传输协议)
- IPPROTO_UDP(UDP传输协议)
- IPPROTO_STCP(STCP传输协议)
- IPPROTO_TIPC(TIPC传输协议)
SOCK_STREAM类型的套接口为全双向的字节流。对于流类套接口,在接收或发送数据前必需处于已连接状态。用connect()调用建立与另一套接口的连接,连接成功后,即可用send()和recv()传送数据。当会话结束后,调用closesocket()。带外数据根据规定用send()和recv()来接收。
实现SOCK_STREAM类型套接口的通讯协议保证数据不会丢失也不会重复。如果终端协议有缓冲区空间,且数据不能在一定时间成功发送,则认为连接中断,其后续的调用也将以WSAETIMEOUT错误返回。
SOCK_DGRAM类型套接口允许使用sendto()和recvfrom()从任意端口发送或接收数据报。如果这样一个套接口用connect()与一个指定端口连接,则可用send()和recv()与该端口进行数据报的发送与接收。
返回值
返回值:socket的编号,为-1表示失败,成功为一个大于0的数字。
socket返回的值是一个文件描述符,SOCKET类型本身也是定义为int的,既然是文件描述符,那么在系统中都当作是文件来对待的,0,1,2分别表示标准输入、标准输出、标准错误。所以其他打开的文件描述符都会大于2, 错误时就返回 -1. 这里INVALID_SOCKET 也被定义为 -1
2. 设置socket的属性
setsockopt函数原型:
int setsockopt( int socket, int level, int option_name,const void *option_value, size_t ,ption_len);
功能:设置一个套接字的选项(属性)
第一个参数socket是套接字描述符。
第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level,在套接字级别上(SOL_SOCKET),option_name可以有以下取值:
- SO_RCVTIMEO,设置接收超时时间,该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。
- SO_SNDTIMEO,设置发送超时时间,该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。
int nNetTimeout=5000;//5秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char*)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char*)&nNetTimeout,sizeof(int));
- SO_REUSEADDR,打开或关闭地址复用功能。
此参数经常用于当socket正在关闭,处于TIME_WAIT的状态,而此时你又想继续重用该socket,你可以私用此参数,否则你在调用bind接口的时候会出现bind failed: Address already in use
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
connect函数
功能:将socket客户端连接到服务端的某个IP地址或端口号即可用connect函数
//函数原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//实例
struct sockaddr_in servaddr;
memset(&servaddr, '\0', sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(server_port); // 标注服务器的端口
inet_pton(AF_INET, server_ip, &servaddr.sin_addr); //通过IP地址连接服务器
int con_tab= connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
-
sockdf:socket文件描述符
-
addr:传入参数,指定服务器端地址信息,含IP地址和端口号
-
addrlen:传入参数,传入sizeof(addr)大小
-
返回值:成功返回0,失败返回-1,设置errno
-
connect 函数将激发 TCP 的三次握手过程,而且仅在连接建立成功或出错时才返回。其中出错返回可能有以下几种情况:
-
三次握手无法建立,客户端发出的 SYN 包没有任何响应,于是返回 TIMEOUT 错误。这种情况比较常见的原因是对应的服务端 IP 写错。
-
客户端收到了 RST(复位)回答,这时候客户端会立即返回 CONNECTION REFUSED 错误。这种情况比较常见于客户端发送连接请求时的请求端口写错,因为 RST 是 TCP 在发生错误时发送的一种 TCP 分节。产生 RST 的三个条件是:目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器(如前所述);TCP 想取消一个已有连接;TCP 接收到一个根本不存在的连接上的分节。
-
客户发出的 SYN 包在网络上引起了"destination unreachable",即目的不可达的错误。这种情况比较常见的原因是客户端和服务器端路由不通。
-
sockaddr_in结构体
sockaddr_in 是一个 C 语言中的结构体,用于存储 IPv4 套接字地址。它包含了以下成员变量:
sockaddr_in所在在头文件#include<netinet/in.h>或#include <arpa/inet.h>
struct sockaddr_in
{
sa_family_t sin_family; //地址族
uint16_t sin_port; //端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero; //预留未使用
};
sin_family:地址族,通常设置为 AF_INET
sin_port:端口号,使用网络字节序
sin_addr:IP 地址,使用 in_addr 结构体存储
sin_zero:填充字段,保证 sockaddr_in 结构体与 sockaddr 结构体大小相同 它通常用于设置套接字地址或者接收套接字地址。
sockaddr_in和sockaddr的区别:
使用sockaddr_in设置端口、IP地址以及地址属性等参数,而不是直接使用sockaddr结构体,因为这个结构体是操作系统内核调用的,对于程序员来说不应该是透明的。所以一般的做法是我们设置好sockaddr_in结构体,然后将其显示转换为sockaddr。
inet_pton函数
头文件:#include<arpa/inet.h>
inet_pton 和 inet_ntop 这 2 个函数能够处理 ipv4 和 ipv6
inet_pton() 转换网络主机地址ip(如192.168.1.10)为二进制数值,并存储在struct in_addr结构中
#include<arpa/inet.h>
struct sockaddr_in servaddr;
inet_pton(AF_INET, 10.168.248.245, &servaddr.sin_addr);
3. fcntl设置非阻塞
fcntl - 对一个打开的文件描述符执行一系列控制操作。
fcntl() 对打开的文件描述符fd执行下面操作之一。操作由cmd决定
int func()
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
int flag = fcntl(fd,F_GETFL,0);//获取文件fd当前的状态
//int flag = fcntl(fd,F_GETFL);//不用第3个参数也可以
if(flag<0)
{
perror("fcntl F_GETFL fail");
close(fd);
return -1;
}
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0)//设置为非阻塞态
{
perror("fcntl F_SETFL fail");
close(fd);
return -1;
}
}
-----------------------------------------
#include <fcntl.h>
int sockfd
int flags;
flags= fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
4. recv函数
- 功能:接收服务店或订阅的IP/端口号发过来的数据
int recv( SOCKET s, char *buf, int len, int flags);
参数一:指定接收端套接字描述符;
参数二:指向一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三:指明buf的长度;
参数四:一般置为0;
- 返回值:异步与同步的返回值没有区别
- 成功:>0,返回值大于0,返回值为读取到的字节数。
- 返回值=0,表示另一端关闭了socket
- 返回值= -1 错误,需要获取错误码errno
int read_tab;
unsigned char data[10000];
read_tab= recv(sockfd, data, 10000,0);