Linux开发(四):网络编程API

在这里插入图片描述

目录

一、网络编程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,不涉及协议相关知识!

一、网络编程API

我们知道正常的CS架构,包含以下内容:

在这里插入图片描述

下面依次介绍以上用到的各个API。

1、socket()函数

创建网络套接字。

#include<sys/types>
#include<sys/socket.h>
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

2、bind()函数

将给定的网络地址及套接字绑定到指定的socket套接字上。

#include<sys/types>
#include<sys/socket.h>
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

3、listen()函数

将socket套接字变为监听套接字,准备接受客户端的连接。

//from /usr/include/sys/socket.h
#include<sys/types>
#include<sys/socket.h>
int listen(int socket,int num);

函数参数:

sockfd: socket套接字

num:内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户连接

返回值:

执行成功返回0,失败返回-1

4、connect()函数

客户端主动发送连接请求给服务器。

//from /usr/include/sys/socket.h
#include<sys/types>
#include<sys/socket.h>
int connect(int socket,struct sockaddr* addr,sockelen_t len );

函数参数:

sockfd: socket套接字

addr:服务器地址信息

len:地址信息长度

返回值:

函数执行成功,则与地址为addr的服务器建立连接,并返回0,失败返回-1。

 5、accept()函数

服务器阻塞等待客户端的连接。

//from /usr/include/sys/socket.h
#include<sys/types>
#include<sys/socket.h>
int accept(int socket,struct sockaddr* addr,sockelen_t len );

函数参数:

sockfd: 已经绑定服务器端口地址信息的socket套接字

addr:用来存储接受到的客户端的端口和地址

len:客户端地址信息的长度

返回值:

成功则返回一个新的socket套接字,从而使原套接字继续等待其他客户端的连接,服务器通过读写该socket来与对应的客户端通信,失败则返回-1。

6、send()/sendto()/sendmsg()函数

向另一个套接字传递消息,其中send 只能用在面向连接(SOCK_STREAM)类型的socket上,而 sendto 和 sendmsg 可用于任何情况下。

#include <sys/types.h>
#include <sys/socket.h>

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

7、recv()/recvfrom()/recvmsg()函数

从指定套接字接收消息,其中recv()只能用在面向连接(SOCK_STREAM)类型的socket上,而 sendto 和 sendmsg 可用于任何情况下。

#include <sys/types.h>
#include <sys/socket.h>
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。

8、close()函数

关闭某socket套接字,不过close函数并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接。多进程程序中,一次fork系统调用默认将使父进程中打开的socket引用计数加1,因此我们必须在父进程和子进程中都对该socket执行close调用才能将连接关闭

#include<unistd.h>

int close(int fd);

函数参数:

fd: 待关闭的socket

返回值:

成功返回0,失败返回-1

9、shutdown()函数

立即关闭网络套接字

#include<unistd.h>
int shutdown(int socket,int how);

函数参数:

fd: 待关闭的socket

how: 可根据参数how指定关闭套接字的某一端:

  • how = 0:关闭socket的读端,即往socket写数据使正常的。
  • how = 1:关闭socket的写端,即往socket读数据使正常的。
  • how = 2:读写通道均关闭,同close()。

返回值:

成功返回0,失败返回-1

二、地址转换

1、IPV4地址转换函数

/*将点分十进制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);

2、地址类型转换

/*将存在在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);

3、通过IP地址获取网络号和主机号(IPv4)

/*从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);

4、字节顺序转换函数

/*长字节字节顺序转换函数*/
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);

5、通过套接字获取主机信息

// 已经至少完成绑定工作
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#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;
}

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值