IPv4套接字结构
套接字结构,定义IP地址与端口号,仅用于IPv4,不适用IPv6
#include <netinet/in.h>
struct sockaddr_in{
uint8_t sin_len; //本结构长度,16字节
sa_family_t sin_family; // AF_INET
in_port_t sin_port; //16位端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //未使用的字符数组
};
//32位IP地址结构,sockaddr_in的第四个成员
struct in_addr{
in_addr_t s_addr;
};
通用套接字结构
由于IPv4,IPv6,域套接字的结构不一样,为了兼容性于是有了通用套接字结构
sockaddr通常用于函数的参数转换,结构大小随协议的不同而改变,在实际使用时,函数会要求传入一个结构大小的参数
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
socket函数
socket函数,用来说明协议和返回一个套接字描述符
#include <sys/socket.h>
int socket(int family, int type, int protocol);
family: AF_INET表示IPv4,AF_INET6表示IPv6,AF_LOCOL表示Unix域协议
type: SOCK_STREAM表示字节流,SOCK_DGRAM表示数据报,另外还有SOCK_RAW和SOCK_SEQPACKET
protocol: 一般填0由系统根据前两个参数自动选择. 可选项有IPPROTO_TCP,IPPROTO_UDP,IPPROTO_SCTP
返回值: 称为套接字描述符,与文件描述符类似
connect函数
客户端使用connect函数向服务端发起连接请求
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *servaddr, socklen_t addrlen);
sockfd: 由socket函数返回的套接字描述符
servaddr: 需要通用套接字地址,IPv4或IPv6套接字地址都需要在此转换一下,servaddr对应的套接字地址信息是服务器的地址和端口号
addrlen: 前一个参数的长度
bind函数
服务器端绑定端口用
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
与connect参数类似,不同的是bind中myaddr的套接字地址信息是本地的地址和端口号
listen函数
服务器端在绑定端口后,listen函数将端口设置为可连接状态,同时listen也可用于设置最多客户端连接数量
int listen(int sockfd,int backlog);
backlog用于设置客户端连接数量上限
accept函数
如果当前没有客户端连接上来,函数使进程投入睡眠.当客户端正确完成连接后,进程被唤醒开始处理事务
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
sockfd为socket函数的返回值
cliaddr是一个传出参数,记录着客户端的IP和端口号
addrlen参数既是传入也是传出,函数开始执行时作为传入参数,其值是cliaddr结构的大小,由我们通过sizeof获取,当函数执行完毕时,addrlen被作为传出参数,其值是实际客户端结构的大小
便于理解的例子: nread=read(fd,buff,nleft),我们要从fd中读取nleft个字节到buff中,read函数的返回值是实际读取的字节数,而accept函数相当于把nleft和nread合并在一起,当我们开始read时从nleft得知要读取的字节数,读取完毕后将返回值写到nleft所在的内存上
close函数
关闭套接字描述符
#include <unistd.h>
int close(int sockfd);
转换函数
主机端字节序与网络字节序转换,s表示short,l虽然表示long但只有32位
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint16_t ntohs(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint32_t ntohl(uint32_t host32bitvalue);
IP地址的字符表示与网络字节表示转换
#include <arpa/inet.h>
//老式,用于v4
inet_aton(const char *strptr,struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
char *inet_ntoa(struct in_addr inaddr);
//新式,兼容v4和v6
int inet_pton(int family,const char *strptr,void *addrptr);
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);
获取套接字ip
#include <sys/socket.h>
int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);
int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addrlen);
getsockname: 获取sockfd套接字本地端的ip地址, 存储在localaddr中, addrlen作输入输出
getpeername: 获取sockfd套接字另一端的ip地址, 后两个参数作用同上
例子1.字节序转换
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* test host byteorder and net byteorder */
int main(int argc, const char *argv[])
{
unsigned int num=0x12345678;
unsigned char *p=(unsigned char *)#
printf("%0x %0x %0x %0x
",p[0],p[1],p[2],p[3]);
unsigned int x=htonl(num);
p=(unsigned char *)&x;
printf("%0x %0x %0x %0x
",p[0],p[1],p[2],p[3]);
char *ipstr="192.168.1.1";
/* old function */
struct in_addr addr;
unsigned int netip=inet_addr(ipstr);
printf("netip=%u
",netip);
//addr.s_addr=netip;
inet_aton(ipstr,&addr);
printf("ipstr=%s
",inet_ntoa(addr));
/* new function */
struct in_addr addr2;
inet_pton(AF_INET,ipstr,&addr2);
printf("netip=%u
",addr.s_addr);
char ipstrbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET,&addr2,ipstrbuf,sizeof(ipstrbuf));
printf("ipstrbuf=%s
",ipstrbuf);
return 0;
}
例子2.获取socket连接的本地和对端ip地址及端口
/* client.c */
//connect(sockfd,(struct sockaddr *)&servaddr,len);
struct sockaddr_in localaddr;
socklen_t len=sizeof(localaddr);
/* getsockname getpeername 参数均一致,只能换个名字即可 */
/* server端调用时要放到accept之后 */
if(getsockname(sockfd,(struct sockaddr *)&localaddr,&len) < 0)
err_quit("getsockname");
printf("local addr=%s,local port=%d
",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));