套接口即网络进程的ID;网络通信归根到底即为进程间的通信;套接字中包含了端口号,用来确定进程,一个端口号一次只能分配给一个进程,即端口号与进程是一一对应的;
socket是一个获取网络通信的ID,我们需要配置IP地址和端口。才可通信
IPv4套接字地址结构
IPv4地址结构命名为sockaddr_in,定义在
struct sockaddr_in {
sa_family_t sin_family; //IPV4协议为AF_INET,协议族
in_port_t sin_port; //16位端口号,网络字节序列
struct in_addr sin_addr; //32位IP地址
unsigned char sin_zero[8]; //备用域;
};
struct in_addr{
in_addr_t s_addr; //32位IP地址,网络字节序列
} ;
通用结构:
struct sockaddr{
sa_family_t sa_family;
char sa_data[14]
}
字节排序结构:
#include
uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
一般使用ASCII字符串表示IP地址,也就是点分十进制数表示(218.170.19.1),但在套接字中使用32位网络字节序,保存的是二进制值,因此必须进行地址转换。
#include
in_addr_t inet_addr(const char *straddr); //字符串有效返回网络字节序的IP地址,否则INADDR_NONE
int inet_aton(const char* straddr,struct in_addr *addrp);//字符串有效返回1,否则0
char* inet_ntoa(struct in_addr inaddr); //返回一个字符串指针
int inet_pton(int family, const char* str, void* addr);
//成功返回1,字符串无效返回0,出错返回-1
const char* inet_ntop(int family, const void* addr, char* str, size_t len);
//成功返回结果指针,出错返回NULL
/*说明
*后面两个函数为新函数,支持IPv4和IPv6,family用来指定:AF_INET,AF_INET6
*inet_ntop函数len为目标存储单元大小,str指针就是函数返回值
*/
1、socket 函数
为了执行网络I/O,进程必须做的第一件事就是执行socket函数,指定期望的通信协议类型。套接字是通信端点的抽象,实现端对端之间的通信,访问套接字需要套接字描述符。套接字描述符通过socket 函数获得,这样才能对套接字进行操作。
/*
* 函数功能:创建套接字描述符;
* 返回值:若成功则返回套接字非负描述符,若出错返回-1;
* 函数原型:
*/
#include
int socket(int family, int type, int protocol);
/*
* 说明:
* socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;
* family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:
* (1)AF_INET IPv4因特网域
* (2)AF_INET6 IPv6因特网域
* (3)AF_UNIX Unix域
* (4)AF_ROUTE 路由套接字
* (5)AF_KEY 密钥套接字
* (6)AF_UNSPEC 未指定
*
* type确定socket的类型,常用类型如下:
* (1)SOCK_STREAM 有序、可靠、双向的面向连接字节流套接字
* (2)SOCK_DGRAM 长度固定的、无连接的不可靠数据报套接字
* (3)SOCK_RAW 原始套接字
* (4)SOCK_SEQPACKET 长度固定、有序、可靠的面向连接的有序分组套接字
*
* protocol指定协议,常用取值如下:
* (1)0 选择type类型对应的默认协议
* (2)IPPROTO_TCP TCP传输协议
* (3)IPPROTO_UDP UDP传输协议
* (4)IPPROTO_SCTP SCTP传输协议
* (5)IPPROTO_TIPC TIPC传输协议
*
*/
2、connect 函数
TCP用户用connect函数与服务器建立连接。
/*
* 函数功能:建立连接,即客户端使用该函数来建立与服务器的连接;
* 返回值:若成功则返回0,出错则返回-1;
* 函数原型:
*/
#include
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
* 说明:
* sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符;
* servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址;
* addrlen是目的套接字地址的大小;
*
* 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号,即客户端connect前不一定要调用bind()函数;
3、bind 函数
bind()函数用于将一个网络地址赋予一个套接字,因为套接字在创建之初是没有地址的需要进行赋值,这里的地址一般为网络IP和端口号。
/*
* 函数功能:将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
* 说明:
* sockfd 为套接字描述符;
* addr是一个指向特定协议地址结构的指针,通用套接字结构;
* addrlen是地址结构的长度;
*/
对于 TCP 协议,调用 bind 函数可以指定一个端口号,或指定一个 IP 地址,也可以两者都指定,还可以都不指定。若 TCP 客户端或服务器端不调用bind 函数绑定一个端口号,当调用connect 或 listen 函数时,内核会为相应的套接字选择一个临时端口号。一般 TCP 客户端使用内核为其选择一个临时的端口号,而服务器端通过调用bind 函数将端口号与相应的套接字绑定。进程可以把一个特定的 IP 地址捆绑到它的套接字上,但是这个 IP 地址必须属于其所在主机的网络接口之一。对于 TCP 客户端,这就为在套接字上发送的 IP 数据报指派了源 IP 地址。对于 TCP 服务器端,这就限定该套接字只接收那些目的地为这个 IP 地址的客户端连接。TCP 客户端一般不把 IP 地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源 IP 地址,而所用外出接口则取决于到达服务器端所需的路径。若 TCP 服务器端没有把 IP 地址捆绑到它的套接字上,内核就把客户端发送的 SYN 的目的 IP 地址作为服务器端的源 IP 地址。
4、listen 函数
listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
/*
* 函数功能:接收连接请求;
* 若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include
int listen(int sockfd, int backlog);
/*
* sockfd是套接字描述符;
* backlog是该进程所要入队请求的最大请求数量;
*/
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接字处于 SYN_REVD 状态;
已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态;
5、accept 函数
accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
/* 函数功能:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠;
* 返回值:若成功返回套接字描述符,出错返回-1;
* 函数原型:
*/
#include
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
/*
* 说明:
* 参数 cliaddr 和 addrlen 用来返回已连接的对端(客户端)的协议地址;
*
* 该函数返回套接字描述符,该描述符连接到调用connect函数的客户端;
* 这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接,
* 而是继续保持可用状态并接受其他连接请求;
* 若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址,
* 并且将addrlen设为指向代表这个缓冲区大小的整数指针;
* accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小;
* 若没有连接请求等待处理,accept会阻塞直到一个请求到来;
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。
socket获取网络连接,bind配置端口号和IP地址,listen用来监听客户端请求,accept获取连接。
#include
#include
#include
#include
#include
#include
int main()
{
int s_fd;
s_fd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8888);
inet_aton("127.0.0.1",&s_addr.sin_addr);
bind(s_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int c_fd = accept(s_fd,NULL,NULL);
printf("success\n");
while(1);
return 0;
}
服务端连接并且打印连接设备的IP地址,实现不同电脑的通信
#include
#include
#include
#include
#include
#include
#include
//ssize_t read(int fd, void *buf, size_t count);
int main()
{
int s_fd;
char readbuf[128];//读入缓冲区
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;//用来保存ip客户端地址
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd = socket(AF_INET,SOCK_STREAM,0);
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8666);
inet_aton("192.168.1.88",&s_addr.sin_addr);
bind(s_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int clen = sizeof(struct sockaddr_in);
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
printf("get success,连接设备的IP为%s\n",inet_ntoa(s_addr.sin_addr));//打印客户端ip地址
read(c_fd,readbuf,sizeof(readbuf));
write(c_fd,"hello i am linux",strlen("hello i am linux"));//写入内容
printf("content:%s\n",readbuf);
return 0;
}