PPC的C/C++和人工智能学习笔记
每一篇学习笔记,都只是为了更好地掌握和理解

linux开发(7)_网络编程(2)socket基本API函数

今天学习Linux的网络编程基础的相关结构体和基本的API函数。

 

socket()函数
头文件 #include<sys/types.h>

#include<sys/socket.h>

定义函数 int socket(int domain,int type,int protocol);
函数说明 socket()用来建立一个新的socket,也就是向系统注册,通知系统建立一个通信端口。

/usr/include/bits/socket.h

参数 第一个参数,协议家族, AF_INET:对应ipv4; AF_INET6:对应ipv6

第二个参数,协议类型,流套接字: SOCK_STREAM 对应TCP;数据报套接字: SOCK_DGRAM 对应UDP;原始套接字: SOCK_RAW,可以自行进行协议设计等。

第三个参数,协议编号,一般置为0。一般SOCK_RAW才使用。

返回值 成功则返回打开的socket文件句柄,失败返回-1。
范例 int sock = socket(AF_INET,SOCK_STREAM,0); //创建TCP套接字

int sock = socket(AF_INET,SOCK_DGRAM,0); //创建UDP套接字

 

 

sockaddr_in,sockaddr,in_addr三个结构体
头文件 #include <netinet/in.h>
sockaddr_in struct sockaddr_in {

short   int   sin_family; //2字节(如:AF_INET)

unsigned  short  int   sin_port; //2字节,端口

struct   in_addr   sin_addr;  //4字节,IP地址结构体

unsigned   char   sin_zero[8]; //8字节

};

sockaddr_in

范例

struct sockaddr_in local;

local.sin_family = AF_INET;  //设置协议家族

local.sin_port = htons(4444); //设置端口,转为网络字节序

local.sin_addr.s_addr = inet_addr(“127.0.0.1”);

//inet_addr()是将点分十进制的字符串转为网络字节序的4字节无符号整数。

//或者写:

local.sin_addr.s_addr = htonl(INADDR_ANY);

//INADDR_ANY实际是0,htonl是将本地字节序转为网络字节序,4字节

//使用INADDR_ANY表示该服务端可接受所有与该服务端相关的IP

//比如:服务端的IP为192.168.1.200,也可以是127.0.0.1,那么客户端的sockaddr_in无论设置哪个都可以connect服务端,否则只能是其中一个

bzero(&local.sin_zero, 8);

sockaddr struct   sockaddr   {

unsigned   short   sa_family;    //2字节

char   sa_data[14];     //14字节

};

sockaddr

说明

sockaddr结构体和sockaddr_in结构体都是16字节,可以互相转化。

sockaddr_in是为了方便设置;

而在bind,accept,connect等函数中,都使用sockaddr结构体,所以使用时需要把sockaddr_in结构体转化为sockaddr。

(struct sockaddr*)&local

in_addr struct   in_addr   {

union {

struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

struct { u_short s_w1,s_w2; } S_un_w;

u_long S_addr;

} S_un;

#define s_addr  S_un.S_addr

};

in_addr

说明

struct in_addr就是32位IP地址。 占4个字节。

是网络字节序的无符号整数。

 

 

inet_addr(),inet_aton(),inet_ntoa(),htonl(),htons(),ntohl(),ntohs()函数
头文件 #include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

inet_addr() unsigned long int inet_addr(const char *cp);

将参数cp所指的网络地址字符串转换成网络所使用的二进制数字。网络地址字符串是以数字和点组成的字符串,例如:“163.13.132.68”,也称为点分十进制。

失败返回-1;

比如:”192.168.0.1″转换为(0xC0A80001)。

inet_aton() int inet_aton(const char * cp,struct in_addr *inp);

将参数cp所指的网络地址字符串转换成网络所使用的二进制数字,存入inp结构体。

失败返回0;

inet_ntoa() char * inet_ntoa(struct in_addr in);

将in结构体中的二进制网络地址转为点分十进制字符串;

例:char ip[20]; sprintf(ip,”%s”,inet_ntoa(local.sin_addr.s_addr));

例子: int main(int argc, char *argv[])  {

struct in_addr addr;

if (argc != 2) {

fprintf(stderr, “%s <dotted-address>\n”, argv[0]);

exit(EXIT_FAILURE);

}

if (inet_aton(argv[1], &addr) == 0) {

fprintf(stderr, “Invalid address\n”);

exit(EXIT_FAILURE);

}

printf(“%s\n”, inet_ntoa(addr));

exit(EXIT_SUCCESS);

}

htonl() unsigned long int htonl(unsigned long int hostlong);

将32位本地序转换成网络序。

htons() unsigned short int htons(unsigned short int hostshort);

将16位本地序转为网络序。

ntohl() unsigned long int ntohl(unsigned long int netlong);

将32位网络序转为本地序。

ntohs() unsigned short int ntohs(unsigned short int netshort);

将16位网络序转为本地序。

 

 

bind()函数
头文件 #include<sys/types.h>

#include<sys/socket.h>

定义函数 int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
函数说明 把套接字和地址绑定。
参数 第一个参数sockfd,就是前面创建的套接字socket文件句柄。

第二个参数my_addr,是sockaddr结构体,一般我们在前面定义sockaddr_in结构体,这里用(struct sockaddr*)&sock_addr_in_num强转。

第三个参数是addrlen,是结构体sockaddr的长度,sizeof(struct sockaddr)

返回值 成功则返回0,失败返回-1,错误原因存放errno。
范例 int ret = bind(sock,(struct sockaddr*)&local,sizeof(local));

if(ret<0) { perror(“bind error!”); return 1;}

 

 

listen()函数
头文件 #include<sys/types.h>

#include<sys/socket.h>

定义函数 int listen(int sockfd,int backlog);
函数说明 开始监听。
参数 第一个参数sockfd,就是前面创建的套接字socket文件句柄。

第二个参数backlog指定同时能处理的最大连接要求,如果连接数目达此上限则client端将收到ECONNREFUSED的错误。

系统中有个定义SOMAXCONN,在ubuntu14.04里该值是128。

返回值 成功则返回0,失败返回-1,错误原因存放errno。
范例 int ret = listen(sock,SOMAXCONN);

if(ret<0) { perror(“listen error!”); return 1;}

 

 

accept()函数
头文件 #include<sys/types.h>

#include<sys/socket.h>

定义函数 int accept(int s,struct sockaddr * addr,int * addrlen);
函数说明 用来接受参数s的socket连线。参数s的socket必需先经bind()、listen()函数处理过,当有连线进来时accept()会返回一个新的socket处理代码,往后的数据传送与读取就是经由新的socket处理,而原来参数s的socket能继续使用accept()来接受新的连线要求。连线成功时,参数addr所指的结构会被系统填入远程主机的地址数据,参数addrlen为scokaddr的结构长度。
参数 第一个参数s,就是前面创建的套接字socket文件句柄,经过bind,listen。

第二个参数addr是sockaddr结构体,我们在accept前必须先定义一个结构体struct  sockaddr_in remote,用来存放即将接受连接的远程地址信息。

用(struct sockaddr*)&sock_addr_in_num强转。

第三个参数是addrlen,是结构体sockaddr的长度,sizeof(struct sockaddr),需要注意的是,这里是个指针。

返回值 成功则返回新的socket文件句柄(用来收发数据),失败返回-1,错误原因存于errno。
范例 struct sockaddr_in remote;

int len = sizeof(struct sockaddr);

int sock_accept = accept(sock,(struct sockaddr*)&remote,&len);

if(sock_accept<0) { perror(“accept error!”); return 1;}

//打印远程连接的IP地址和端口

printf(“remote IP=%s,port=%d\n”,inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));

 

 

connect()函数
头文件 #include<sys/types.h>

#include<sys/socket.h>

定义函数 int connect(int sockfd,struct sockaddr * serv_addr,int addrlen);
函数说明 用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址服务端。

这个函数一般用在客户端,用来连接服务端。

参数 第一个参数sockfd,就是前面创建的套接字socket文件句柄。

第二个参数addr是sockaddr结构体,存放的是服务端的网络信息。

用(struct sockaddr*)&sock_addr_in_num强转。

第三个参数是addrlen,是结构体sockaddr的长度,sizeof(struct sockaddr)

返回值 成功则返回0,失败返回-1,错误原因存于errno。
范例 struct sockaddr_in server_addr;

//初始化server_addr的协议家族、IP、PORT等参数

int ret = connect(sock,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));

if(ret<0) { perror(“connect error!”); return 1;}

 

 

send(),recv(),sendto(),recvfrom(),read(),write()函数
头文件 #include<sys/types.h>

#include <sys/socket.h>

#include <unistd.h> //read write

读函数 ssize_t read(int fd, void *buf, size_t count);

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 *src_addr, socklen_t *addrlen);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

   
写函数 ssize_t write(int fd, const void *buf, size_t count);

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,

const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

   
struct msghdr struct iovec {                    /* Scatter/gather array items */

void  *iov_base;              /* Starting address */

size_t iov_len;               /* Number of bytes to transfer */

};

 

struct msghdr {

void         *msg_name;       /* optional address */

socklen_t     msg_namelen;    /* size of address */

struct iovec *msg_iov;        /* scatter/gather array */

size_t        msg_iovlen;     /* # elements in msg_iov */

void         *msg_control;    /* ancillary data, see below */

size_t        msg_controllen; /* ancillary data buffer len */

int           msg_flags;      /* flags on received message */

};

   
Recv说明: recv函数:ssize_t recv(int sockfd, void *buf, size_t len, int flags);

第二个参数指明一个缓冲区,用来存放recv函数接收到的数据;

第三个参数指明buf的长度;第四个参数一般置0(异步设置为MSG_DONTWAIT)。

同步socket的recv函数的执行流程:当应用程序调用recv函数时,

(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回错误-1;

(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)。

recv函数返回其实际copy的字节数。

如果recv在copy时出错,那么它返回-1;

如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

返回值:<0 出错;==0对方关闭连接 ;>0 接收到数据大小。

 

注意:返回值<0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续接收。只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要循环读取)。

 

 

close(),shutdown()函数
头文件 #include<sys/types.h>

#include <sys/socket.h>

close() int close(int sockfd);     //返回成功为0,出错为-1

说明:close一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字描述符不能再由调用进程使用,TCP将尝试发送已经在发生缓冲区中的数据到对端,发送完毕后进行正常的TCP连接终止序列(发送FIN包)。

在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相应的减一,当引用计数仍大于零时,这个close调用就不会引发TCP的四路握手断连过程。只有最后关闭的那个进程close才会引发断连!

shutdown() int shutdown(int sockfd,int howto);  //返回成功为0,出错为-1.

Howto参数:

SHUT_RD:值为0,关闭连接的读这一半。

SHUT_WR:值为1,关闭连接的写这一半。

SHUT_RDWR:值为2,连接的读和写都关闭。

区别 close函数关闭套接字,如果有其他的进程共享着这个套接字,那么它仍然是打开的,其他进程仍然可以用来读和写。

而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些尝试读的进程将会接收到EOF标识,那些尝试写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。

 

 

 

(2017-11-08 www.vsppc.com)

学习笔记未经允许不得转载:PPC的C/C++和人工智能学习笔记 » linux开发(7)_网络编程(2)socket基本API函数

分享到:更多 ()

评论 抢沙发

评论前必须登录!