目录
一、网络编程API
1、socket()函数
2、bind()函数
3、listen()函数
4、connect()函数
5、accept()函数
6、send()/sendto()/sendmsg()函数
7、recv()/recvfrom()/recvmsg()函数
8、close()函数
9、shutdown()函数
二、地址转换
1、IPV4地址转换函数
2、地址类型转换
3、通过IP地址获取网络号和主机号(IPv4)
4、字节顺序转换函数
5、通过套接字获取主机信息
三、示例
本文主要介绍Linux网络编程时,用到的各个API,不涉及协议相关知识!
我们知道正常的CS架构,包含以下内容:
下面依次介绍以上用到的各个API。
创建网络套接字。
#include
#include
int socket(int domain, int type, int protocol);
函数参数:
domain:该socket要使用的地址协议簇,常用选项如下:
- #define PF_LOCAL 1 /* 本地通信 ,即AF_LOCAL*/
- #define PF_UNIX PF_LOCAL /* 本地通信 */
- #define PF_FILE PF_LOCAL /* 本地通信 */
- #define PF_INET 2 /* IPV4协议簇,即AF_INET */
- #define PF_AX25 3 /* AX.25 */
- #define PF_IPX 4 /* Novell网协议 */
- #define PF_INET6 10 /* IPV6协议簇 */
type:指定socket套接字的类型,可以取以下值之一:
- SOCK_STREAM (字节流套接字--TCP)
- SOCK_DGRAM (数据报套接字---UDP)
- SOCK_SEQPACKET (有序分组套接字)
- SOCK_RAW (原始套接字)
protocol:传输协议类型,写0即可,由系统自行选择,可以取以下值之一,正常情况下:
- IPPROTO_TCP (tcp传输协议)
- IPPROTO_UDP (udp传输协议)
- IPPROTO_SCTP (sctp传输协议)
返回值:
成功返回一个socket文件描述符,失败返回-1
将给定的网络地址及套接字绑定到指定的socket套接字上。
#include
#include
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
函数参数:
sockfd: socket套接字
my_addr: 一个指向strcut sockaddr的指针,根据socket不同的协议类型,其结构也不同,原始定义相对复杂,下面是我整理后的结构体定义,根据不同类型可以直接使用:/* Structure describing a generic socket address. */ struct sockaddr { uint16 sa_family; /* 通用数据*/ char sa_data[14]; /* Address data. */ }; /* IPv4*/ struct sockaddr_in { uint16 sin_family; /* 通用数据*/ uint16 sin_port; /* Port number. */ uint32 sin_addr.s_addr; /* Internet address. */ unsigned char sin_zero[8]; /* Pad to size of `struct sockaddr'. */ }; /* Ditto, for IPv6. */ struct sockaddr_in6 { uint16 sin6_family; /* 通用数据*/ uint16 sin6_port; /* Transport layer port # */ uint32 sin6_flowinfo; /* IPv6 flow information */ uint8 sin6_addr[16]; /* IPv6 address */ uint32 sin6_scope_id; /* IPv6 scope-id */ };
addrlen: sizeof(struct sockaddr)的大小
返回值:
成功返回0,失败返回-1
将socket套接字变为监听套接字,准备接受客户端的连接。
//from /usr/include/sys/socket.h
#include
#include
int listen(int socket,int num);
函数参数:
sockfd: socket套接字
num:内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户连接
返回值:
执行成功返回0,失败返回-1
客户端主动发送连接请求给服务器。
//from /usr/include/sys/socket.h
#include
#include
int connect(int socket,struct sockaddr* addr,sockelen_t len );
函数参数:
sockfd: socket套接字
addr:服务器地址信息
len:地址信息长度
返回值:
函数执行成功,则与地址为addr的服务器建立连接,并返回0,失败返回-1。
服务器阻塞等待客户端的连接。
//from /usr/include/sys/socket.h
#include
#include
int accept(int socket,struct sockaddr* addr,sockelen_t len );
函数参数:
sockfd: 已经绑定服务器端口地址信息的socket套接字
addr:用来存储接受到的客户端的端口和地址
len:客户端地址信息的长度
返回值:
成功则返回一个新的socket套接字,从而使原套接字继续等待其他客户端的连接,服务器通过读写该socket来与对应的客户端通信,失败则返回-1。
向另一个套接字传递消息,其中send 只能用在面向连接(SOCK_STREAM)类型的socket上,而 sendto 和 sendmsg 可用于任何情况下。
#include
#include
int send(int sockfd, const void *msg, size_t len, int flags);
int sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
int sendmsg(int sockfd, const struct msghdr * mhdr, int flags);
函数参数:
sockfd: 用于传输数据的socket描述符
msg: 待发送的数据缓冲区
len: 数据缓冲区长度
mhdr:是msghdr结构体类型的指针, msghdr结构体的定义如下:
struct msghdr { void* msg_name; /*socket地址*/ socklen_t msg_namelen; //socket地址的长度 struct iovec* msg_iov; //分散的内存块,见后文 int msg_iovlen; //分散内存块的数量 void* msg_control; //指向辅助数据的起始位置 socklen_t msg_controllen; //辅助数据的大小 int msg_flags; //复制函数中的nags参数,并在调用过程中更新 }; struct iovec { void *iov_base; //内存起始地址 size_t iov_len; //这块内存的长度 }
flags:为数据收发提供了额外的控制,它可以取表所示选项中的一个或几个的逻辑或:
to: 指向存放接收端的地址信息,当socketfd的类型为面向连接(SOCK_STREAM)类型时,该值必须为NULL,否则将报错
tolen:地址信息长度,当socketfd的类型为面向连接(SOCK_STREAM)类型时,该值必须为0,否则将报错
返回值:
成功时,返回实际发送的字符数,失败返回 -1
从指定套接字接收消息,其中recv()只能用在面向连接(SOCK_STREAM)类型的socket上,而 sendto 和 sendmsg 可用于任何情况下。
#include
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *from, socklen_t *fromlen);
ssize_t recvmsg(int sockfd, struct msghdr *mhdr, int flags);
函数参数:
sockfd: 用于传输数据的socket描述符
msg: 用于存储接收到的数据缓冲区
len: 数据缓冲区长度
mhdr:是msghdr结构体类型的指针, msghdr结构体的定义如下:
struct msghdr { void* msg_name; /*socket地址*/ socklen_t msg_namelen; //socket地址的长度 struct iovec* msg_iov; //分散的内存块,见后文 int msg_iovlen; //分散内存块的数量 void* msg_control; //指向辅助数据的起始位置 socklen_t msg_controllen; //辅助数据的大小 int msg_flags; //复制函数中的nags参数,并在调用过程中更新 }; struct iovec { void *iov_base; //内存起始地址 size_t iov_len; //这块内存的长度 }
flags:为数据收发提供了额外的控制,它可以取表所示选项中的一个或几个的逻辑或:
返回值:
这些调用返回接收到的字节数,如果发生错误,则返回 -1。当对端执行了有序关闭时,返回值将为 0。
关闭某socket套接字,不过close函数并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接。多进程程序中,一次fork系统调用默认将使父进程中打开的socket引用计数加1,因此我们必须在父进程和子进程中都对该socket执行close调用才能将连接关闭
#include
int close(int fd);
函数参数:
fd: 待关闭的socket
返回值:
成功返回0,失败返回-1
立即关闭网络套接字
#include
int shutdown(int socket,int how);
函数参数:
fd: 待关闭的socket
how: 可根据参数how指定关闭套接字的某一端:
- how = 0:关闭socket的读端,即往socket写数据使正常的。
- how = 1:关闭socket的写端,即往socket读数据使正常的。
- how = 2:读写通道均关闭,同close()。
返回值:
成功返回0,失败返回-1
/*将点分十进制IP地址转换为32位网络字节顺序的IP地址*/
int inet_addr(const char* cp);
/*将点分十进制IP地址转换为32位主机字节顺序的IP地址*/
int inet_network(const char* cp);
/*将点分十进制的字符串转换为32位网络字节顺序的IP地址*/
char* inet_aton(const char* cp,struct in_addr in);
/*将32位网络字节顺序的IP地址转换为点分十进制的字符串*/
char* inet_ntoa(struct in_addr in);
/*将存在在cp位置,地址类型为af的点分十进制地址转换到buf中,若af为IPv4,则buf应为struct in_addr,若af为IPv6,则buf应为struct in6_addr*/
int inet_pton(int af,const char* cp,void* buf);
/*将存储在位置cp,地址协议为af的网络字节序的32位IP地址转换为点分十进制,并存储在长度为len的buf中,若af为IPv4,则cp应为struct in_addr类型,若af为IPv6,则cp应为struct in6_addr类型*/
char* inet_ntop(int af,void* cp,char* buf,socketlen_t len);
/*从IP地址in(32位网络字节序)中提取标准主机号*/
int inet_lnaof(struct in_addr in);
/*从IP地址in(32位网络字节序)中提取标准网络号*/
int inet_netof(struct in_addr in);
/*将网络号(网络字节序)和主机号(网络字节序)组合成一个标准IP地址(网络字节序)*/
struct in_addr inet_makeaddr(int net,int host);
/*长字节字节顺序转换函数*/
unsigned long int ntohl(unsigned long int net);
unsigned long int htonl(unsigned long int host);
/*短字节字节顺序转换函数*/
unsigned short int ntohl(unsigned short int net);
unsigned short int htonl(unsigned short int host);
// 已经至少完成绑定工作
int getsockname(int socket,struct sockaddr* addr,socklen_t* len);
// 已经成功连接上服务器
int getpeername(int socket,struct sockaddr* addr,socklen_t* len);
以下是自己写的一个C/S架构的简单例子,主要练习以上函数的用法:
server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVERPORT 8888
#define SERVERADDR "192.168.103.128"
void* recvmsgs(void* pSockfd)
{
char buf[1024];
int* socket;
if(NULL == pSockfd)
{
printf("recvmsg fail!\n");
return;
}
socket= (int*)pSockfd;
while(1)
{
memset(buf,0,sizeof(buf));
recv(*socket,buf,1024,0);
printf("recv a msg:%s\n",buf);
sleep(1);
}
return;
}
int main()
{
int opt = 1;
int ret = -1;
socklen_t len;
pthread_t tid = -1;
int newfd = -1;
int sockfd = -1;
char buffer[1024];
struct sockaddr_in server,clienaddr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket fail!\n");
return -1;
}
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(SERVERPORT);
server.sin_addr.s_addr = INADDR_ANY;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(-1 == bind(sockfd,(struct sockaddr*)&server,sizeof(server)))
{
perror("connect fail!\n");
return -1;
}
if(-1 == listen(sockfd,1))
{
perror("listen error!\n");
return -1;
}
len = sizeof(struct sockaddr);
while((newfd= accept(sockfd,(struct sockaddr*)&clienaddr,&len)) != -1)
{
printf("newfd:%d,clienaddr:sin_family=%d,sin_port=%d,s_addr=%s\n",newfd,clienaddr.sin_family,ntohs(clienaddr.sin_port),inet_ntoa(clienaddr.sin_addr));
ret = pthread_create(&tid,NULL,recvmsgs,(void*)&newfd);
if(-1 == ret)
{
perror("pthread_create fail\n");
return -1;
}
}
return 0;
}
client.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVERPORT 8888
#define SERVERADDR "192.168.103.128"
void* recvmsgs(void* pSockfd)
{
char buf[1024];
int* socket;
if(NULL == pSockfd)
{
printf("recvmsg fail!\n");
return;
}
socket= (int*)pSockfd;
while(1)
{
memset(buf,0,sizeof(buf));
recv(*socket,buf,1024,0);
printf("recv a msg:%s\n",buf);
sleep(1);
}
return;
}
int main()
{
int ret = -1;
pthread_t tid = -1;
pthread_t tid2 = -1;
int sockfd = -1;
char buffer[1024];
struct sockaddr_in server;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket fail!\n");
return -1;
}
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(SERVERPORT);
server.sin_addr.s_addr = inet_addr(SERVERADDR);
if(-1 == connect(sockfd,(struct sockaddr*)&server,sizeof(server)))
{
perror("connect fail!\n");
return -1;
}
ret = pthread_create(&tid,NULL,recvmsgs,(void*)&sockfd);
if(-1 == ret)
{
perror("pthread_create fail\n");
return -1;
}
while(1)
{
memset(buffer,0,sizeof(buffer));
gets(buffer);
send(sockfd,buffer,1024,0);
sleep(1);
}
return 0;
}