Linux下的TCP套接字编程
客户端
基本流程
创建socket套接字
//函数原型
#include<sys/types.h>
#include <sys/socket.h>
int socket(int family, int type, int protocol);
/*返回值
成功:非负描述字,即非负整数值,称为套接字。同文件描述符类似
出错:-1*/
/*参数
family:协议族,一般都是AF_INET(ipv4协议)或者PF_INET6(ipv6协议)
type:套接字通信类型,一般是SOCK_STREAM(tcp)或者SOCK_DGRAM(udp)
protocol:制定某个协议的特定类型,一般设置为0
*/
设置与服务器ip和服务器端口相关的数据结构
#include <netinet/in.h>
struct in_addr {
in_addr_t s_addr;
};
struct sockaddr_in {
uint8_t sin_len;//地址结构长度
sa_family_t sin_family; //协议族,与socket函数第一个参数相同
in_port_t sin_port; //端口号
struct in_addr sin_addr; //IP地址
char sin_zero[8]; //填充字段
};
sockaddr_in serverAddress;
//使用前要将其清零
memset(&serverAddress, 0, sizeof(sockaddr_in);
//端口赋值
serverAddress.sin_port = htons(4000);//使用htons()函数是因为主机字节序列(小端)和网络字节序列(大端)不同
//服务器ip地址赋值
#include <arpa/inet.h>
int inet_aton(const char* strptr, struct in_addr *addrptr);//将strptr指向的字符串,转换成32位的网络字节序二进制值,并存储在addrptr指向的结构体中
in_addr_t inet_addr(const char* strptr); //将strptr指向的字符串,转换成32位的网络字节序二进制值,将其作为返回值返回
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
int inet_pton(int family, const char* strptr, void *addrptr);//addrptr:转换strptr指向的字符串,将网络字节序的二进制值,存储在addrptr指向的内存
//通常addrptr指向sockaddr_in或sockaddr_in6
inet_pton(AF_INET, strServerIP, &serverAddress.sin_addr)
连接服务器
//函数原型
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
connect(nClientSocket, (sockaddr*)&ServerAddress, sizeof(ServerAddress))
/*
connect激发TCP的三路握手过程;仅在连接建立成功或出错时才返回
其中错误有以下几种情况:
如果客户没有收到SYN分节的响应(总共75秒,这之间需要可能需要重发若干次SYN),则errno被设置为ETIMEDOUT(等于110)。
如果对客户的SYN的响应是RST,则表明该服务器主机在该端口上没有进程在等待。errno被设置成ECONNREFUSED(等于111);
如果客户发出的SYN在中间路由器上引发一个目的地不可达ICMP错误,则如第一种情况,连续发送SYN,直到规定时间仍未收到响应,则errno被设置成EHOSTUNREACH(113)或ENETUNREACH(101)。
如果函数connect失败,则套接字不可再用,必须关闭。不能再对此套接字再调用函数connect。
*/
服务器端
基本流程
创建socket套接字
同客户端
设置与服务器ip和服务器端口相关的数据结构
同客户端
ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);//设置为本地ip
绑定套接字和服务器ip及端口
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
bind(nListenSocket, (sockaddr *)&ServerAddress, sizeof(sockaddr_in));
//返回值:成功为0,出错为-1
/*
TCP服务器或客户端,都可以不调用或调用bind函数
TCP服务器通常都要调用bind函数,绑定在一个预先设置好的端口上
若TCP服务器不调用bind函数,当调用listen函数时,内核将自动分配一个端口
TCP客户端通常都不要调用bind函数,当调用connect函数时,内核自动分配一个端口
*/
监听
//函数原型
#include<sys/socket.h>
int listen(int sockfd, int backlog);
//返回值:成功返回0,出错返回-1
//listen的第二个参数规定了内核应该为相应套接口排队的最大连接个数
/*当调用完listen函数,无论服务器是否调用accept函数(从监听队列中取出一个连接)
客户端都可以向服务器发送数据,数据将保存在已连接套接口的接收缓冲区中*/
listen(nListenSocket, nLengthOfQueueOfListen);
接受客户端连接
//函数原型
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
//cliaddr:当accept函数返回时,内核将从已完成连接队列中,取出一个连接;并将该连接的客户端信息(协议族、IP、Port),保存在cliaddr指向的结构体中
//可以将cliaddr和addrlen设置为NULL
sockaddr_in ClientAddress;
socklen_t LengthOfClientAddress = sizeof(sockaddr_in);
int nConnectedSocket = ::accept(nListenSocket, (sockaddr *)&ClientAddress, &LengthOfClientAddress);
其他接口
获取本地和远端协议地址
//函数原型
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
/*参数
sockfd:需要查询的套接字
localaddr:该函数返回时,填充该结构
addrlen:调用该函数之前,让*addrlen为localaddr指向的套接口地址结构大小;调用之后,为实际套接口地址结构大小
0:成功,-1:出错*/
//函数原型
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
/*返回远端的地址或端口信息(服务器调用,返回客户,客户调用返回服务器)
参数
sockfd:需要查询的套接字
peeraddr:该函数返回时,填充该结构
addrlen:调用该函数之前,让*addrlen为localaddr指向的套接口地址结构大小;调用之后,为实际套接口地址结构大小
0:成功,-1:出错*/
接收数据
#include <unistd.h>
int read(int fd, char *buf, int len);
/* 返回:大于0-读写字节大小;-1-出错;
调用函数read时,有如下几种情况:
套接字接收缓冲区接收到数据,返回接收到的字节数;
tcp协议收到FIN数据,返回0;
tcp协议收到RST数据,返回-1,同时errno为ECONNRESET;
进程阻塞过程中接收到信号,返回-1,同时errno为EINTR。*/
发送数据
#include <unistd.h>
int write(int fd, char *buf, int len);
/* 返回:大于0-读写字节大小;-1-出错;
调用函数write,有如下几种情况:
套接字发送缓冲区有足够空间,返回发送的字节数;
tcp协议接收到RST数据,返回-1,同时errno为ECONNRESET; ;
进程阻塞过程中接收到信号,返回-1,同时errno为EINTR。*/