C语言socket编程

文章目录

  • 基本套接字函数(8个)
    • socket()
    • socketpair()
    • bind()
    • listen()
    • connect()
    • accept()
    • read()&write()
  • 高级套接字函数
    • send()&sendto()&recv()&recvfrom()
    • sendmsg()&recvmsg()
    • readv()&writev()
    • close()&shutdown()
    • gethostname()
    • getpeername()
    • getsockname()
    • getsockopt()&setsockopt()
    • fcntl()
    • ioctl()
  • 多路复用
    • select()
    • FD_SET()
    • FD_CLR()
    • FD_ZERO()
    • FD_ISSET()
  • 网络服务器分类
    • 循环服务器
    • 并发服务器
  • 信号机制
    • 进程四要素
    • 守护进程
    • 信号来源
  • IPC
  • OOB
  • IO模型

基本套接字函数(8个)

socket()

#include
#include
int socket(int family,int type,int protocol);
//返回套接字描述符,失败则返回-1.
//该函数实际为socket数据结构分配空间
int fd;
fd=socket(AF_INET,SOCK_STREAM,0);

family指定协议簇:

AF_INET,ipv4

​ AF_UNIX,UNIX 进程通信协议

​ AF_ISO,iso协议簇

​ AF_INET6,ipv6

type:表示套接字类型:

SOCK_STREAM,双向连续且可信赖虚电路服务,tcp。

​ SOCK_DGRAM,不连续不可信赖数据报服务,udp。

​ SOCK_SEQPACKET,连续且可信赖,有序分组套接字。

​ SOCK_RAW,原始套接字。

​ SOCK_RDM,能可靠交付信息的数据报套接字。

protocol:指定协议。通常为0,即默认协议。

socketpair()

#include
#include
int socketpair(int family,int type,int protocol,int fd_array[2]);
//建立一对套接字描述符而不是文件描述符。成功则返回2,失败则返回-1。

family只能为AF_UNIX或AF_LOCAL。

这里两个套接字描述符是双向的,而管道是单向的。

bind()

#include
#include
int bind(int fd,struct sockaddr* addr,int addrlen);
//成功则返回0,失败则返回-1.

fd是socket()返回的套接字描述符。

addr是struct sockaddr结构体的指针。

addrlen为sizeof(struct sockaddr)

#include
#include
struct sockaddr{
  unsigned short sa_family;   //AF_XXXX
  char           sa_data[14]; //address
};

struct sockaddr_in{
  short sin_family;   //AF_XXXX
  unsigned short sin_port;//using htons().
  struct in_addr sin_addr;//using 
  unsigned char sin_zero[8]; //填充以与sockaddr对齐
};//解决了struct sockaddr的缺陷,分开地址和端口。
//调用bind(),accept()等函数时需把struct sockaddr_in*转换为struct sockaddr*
 struct in_addr{
  in_addr_t s_addr;    //32位的IPv4地址(typedef uint32_t in_addr_t;)
};

要转换为网络字节顺序(NBO)的地址和端口类型有两种,short(2字节)和long(4字节)。

sin_family 没有发送到网络上,它们可以是本机字节顺序。

htons()–“Host to Network Short”

htonl()–“Host to Network Long”

ntohs()–“Network to Host Short”

ntohl()–“Network to Host Long”

而__ip地址这样转为NBO__

myAddr.sin_addr.s_addr=inet_addr("192.168.0.0")

若要转为点格式(ascii):printf("%s\n",inet_ntoa(myAddr.sin_addr))

注意:每次调用 inet_ntoa(),它就将覆盖上次调用时所得的IP地址,即静态指针。

以上转换函数头文件arpa/inet.hwinsock2.h.

注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。

//sockaddr_in和bind用法
struct sockaddr_in myAddr;
bzero(&myAddr,sizeof(myAddr));//推荐
memset(&myAddr,0sizeof(myAddr));
myAddr.sin_family=AF_INET;
myAddr.sin_port=htons(16);//0x0010-->0x1000,实际应用应大于1024.0端口为自己选择端口。
if(myAddr.sin_addr.s_addr=htonl(INADDR_ANY)<0) //0x00000000,任意网络设备接口,服务器常用。
{
  perror("inet_addr error.");exit(1);
}
/*或
if (inet_aton(argv[1], (struct in_addr *)&myAddr.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }
*/
if(bind(myAddr,(struct sockaddr*)&saddr,sizeof(saddr))<0)
  {
  perror("bind error");
  exit(1);
}

listen()

#include 
int listen( int sockfd, int qlen);
//sockfd只能是SOCK_STREAM,SOCK_SEQPACKET类型
//qlen为请求队列长度,通常为20.
//错误返回-1

至此,服务器端调用顺序为socket(),bind(),listen()

connect()

#include
#include
int connect(int sockfd,struct sockaddr *addr,int addrlen);
//客户端函数,用来连接服务器。
//错误时返回-1。
if(connect(mySock,(struct sockaddr*)&serveAddr,sizeof(serveAddr))<0)
{
  perror("connect error");
  exit(1);
};
//客户端不需要bind(),即端口不需要指定。

accept()

#include
#include
int accept(int sockfd, struct sockaddr*addr, int* addrlen);
//成功则返回新的socket 处理代码, 失败返回-1, 错误原因存于errno 中
clientSock=accept(serveSock,(struct sockaddr *)&clientAddr,&sizeof(struct sockaddr_in));
if clientSock<0
{
	perror("client accept error.");
	exit(1);
}

read()&write()

#include
int read(int sockfd,char*buf,int len);
int write(int sockfd,char*buf,int len);

高级套接字函数

send()&sendto()&recv()&recvfrom()

#include
#include
//typedef int ssize_t;(signed size_t)
ssize_t send(int sockfd,const void* msg,size_t len,int flags);
ssize_t sendto(int sockfd,const void* msg,size_t len,int flags,struct sockaddr *dest_addr,int addrlen);
ssize_t recv(int sockfd,const void* msg,size_t len,int flags);
ssize_t recvfrom(int sockfd,const void* msg,size_t len,int flags,struct sockaddr *src_addr,int addrlen);
//falgs为0时,功能同write()和read().
//错误的时候返回-1,并设置 errno。

sendmsg()&recvmsg()

#include
#include
#include
struct iovec{
  caddr_t	iov_base;//buf起始地址,typedef void* caddr_t.
  int		iov_len;     //buf长度
}struct msghdr{
  caddr_t	msg_name; //目的套接字地址
  int		msg_namelen;
  struct iovec* msg_iov;//包含msg
  int		msg_iovlen;
  caddr_t	msg_accrights;//访问权限
  int		msg_addrightslen;
}
int sendmsg(int sockfd,struct msghdr* msgp,int flags);
int recvmsg(int sockfd,struct msghdr* msgp,int flags);

sendmsg可以规格化数据以及发送被中断了的数据。

readv()&writev()

#include
#include
ssize_t readv(int fd,const struct iovec *iov,int iovcnt);
ssize_t writev(int fd,const struct iovec *iov,int iovcnt);
//一次读写多个非连续缓存,又称散布读(scatter read)和聚集写(gather write).
//成功则返回读写字节数,失败则返回-1.

close()&shutdown()

#include
int close(int fd);

#include
int shutdown(int fd,int how);
//how
//0:停止读
//1:停止写
//2:停止读写
//成功则返回0,失败则返回-1.

gethostname()

#include
#include
int gethostname(char*hostname,size_t len);
//成功时返回 0,失败时返回 -1,并设置errno。

getpeername()

#include
#include
int getpeername(int sockfd,struct sockaddr *addr, socklen_t *addrlen);
//获得与指定套接字连接的对等进程名。

getsockname()

#include
#include
int getsockname(int sockfd,struct sockaddr *addr, socklen_t *addrlen);
//返回与套接字连接的本地进程的地址和进程元素。

getsockopt()&setsockopt()


int getsockopt(int sockfd, int level, int optname, void *optval,sock_len_t*optlen);
int setsockopt(int sockfd, int level, int optname, void *optval, sock_len_t optlen);

level -选项级别

  • SOL_SOCKET — 通用 socket 选项

  • IPPROTO_TCP—TCP 选项

  • IPPROTO_IP—IP 选项

optname— 选项名称

  • SO_KEEPALIVE,v 设置该选项后,一段时间(通常 2 小时)内没有数据交换时, TCP 协议将自动发送探测数据包,检查网络连接.
  • SO_RCVBUF 和 SO_SNDBUF,v 发送和接收数据缓冲区的大小(在连接建立以前设置).
  • SO_RCVTIMEO 和 SO_SNDTIMEO,v 发送和接收超时,当指定时间内数据没有成功接收或发送,发送和接收函数将返回.
  • SO_REUSEADDR,v 一般一个端口释放后会等待两分钟才能被再次使用,该选项可让端口释放后立即就可以被再次使用,这通常在重启监听服务器时可避免 bind 函数出错v 允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地 IP 地址即可.

fcntl()

int fcntl(int fd,int cmd,);

功能:改变套接字属性

  • 设置 socket 为阻塞 / 非阻塞模式

  • 设置允许 / 不允许接收异步 I/O 信号

  • 设置 / 获取 socket 的所有者

返回值: ≥0 -成功, -1 -失败

cmd 参数 ret 说明
F_GETFL 0 描述符标志 获得描述符标志
F_SETFL O_NONBLOCK 成功0,否则-1 设置socket为非阻塞方式
O_ASYNC 成功0,否则-1 设置允许SIGIO异步通知
F_GETOWN int* 成功0,否则-1 获得socket的所有者
F_SETOWN int 成功0,否则-1 设置socket的所有者

ioctl()

功能:控制输入输出

  • req -执行的操作类型

  • 第三个参数-总是指针类型,存储操作返回的数据或操作所需的数据

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

man 2 ioctl_list

req 参数 说明
SIOCATMARK int* 检测是否到达带外标记
FIONBIO int* 非阻塞模式
FIOASYNC int* 异步输入/输出标志
SIOCSPGRP int* 设置/获取目标进程或进程组
/SIOCGPGRP
FIONREAD int* 缓冲区中有多少字节数据可读

多路复用

通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

select()

int select(
  int maxfd,
  fd_set *rdset,
  fd_set *wrest,
  fd_set *exset,
  struct timeval *timeout);

返回值:

  • 有描述符就绪则返回就绪的描述符个数;超时时间内没有描述符就绪返回 0 ;执行失败返回-1 。

参数:

  • maxfd -集合中所有描述符的最大值 +1

  • rdset -需要测试是否可读的描述符集合(包括处于listen 状态的 socket 接收到连接请求)

  • wrset -需要测试是否可写的描述符集合(包括以非阻塞方式调用 connect 是否成功)

  • exset -需要测试是否异常的描述符集合(包括接收带外数据的 socket 有带外数据到达)

  • timeout -指定测试超时的时间 .若将 NULL 以形参传入,即不传入时间结构,就是将
    select 置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
    若将时间值设为__0 秒 0 毫秒___,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回 0 ,有变化返回一个正值.timeout 的值__大于 0__,则该时间内阻塞。

功能:

检查多个文件描述符( socket 描述符)是否就绪,当某一个描述符就绪(可读、可写或发生异
常)时函数返回,可以实现输入输出多路复用.

select()把一些文件描述符集合在一起,如果某个文件描述符的状态发生变化,比如进入“写就绪”或者“读就绪”状态,函数 select 会立即返回,并且通知进程读取或写入数据;如果没有 I/O 到达,进程将进入阻塞,直到函数 select 超时退出为止。

FD_SET()

void FD_SET(int fd,fd_set* fdset);

将一个描述符添加到描述符集合

FD_CLR()

void FD_CLR(int fd,fd_set* fdset);

将一个描述符从描述符集合中清除

FD_ZERO()

void FD_ZERO(fd_set* fdset)

清空描述符集合

FD_ISSET()

int FD_ISSET(int fd,fd_set* fdset)

检查描述符是否在集合中,如果在集合中返回非 0 值,否则返回 0.

在设置描述符集合前应该先调用 FD_ZERO 将集合清空,每次调用 select 函数前应该重新设置读、写和错
误 3 个集合;三个集合中的描述符可以交叉.

网络服务器分类

循环服务器

同一时刻只能处理一个客户端请求

并发服务器

同一时刻可以处理多个客户端请求

一般是UDP循环,TCP并发。

信号机制

进程四要素

要有一段程序供该进程运行;
进程专用的系统堆栈空间;
进程控制块 PCB , Linux 下为 task_struct 结构;
有独立的存储空间

守护进程

守护进程特点:
通常在系统初始化时被启动;
生存期为系统执行时间;
一直等待事件发生并处理事件;
可以利用其他进程完成各种请求;
一般不和终端发生联系

信号来源

信号来源:
通过系统调用,比如 kill :int kill(int pid, int sig);
通过 kill 命令,比如 kill -9 pid 发送 SIGKILL 信号;
特定键盘字符,比如 ctrl+c 产生 SIGINT 信号;
硬件故障,比如 SIGFPE 为浮点算术错信号;
某些软件产生,比如加急数据到达的 SIGURG 信号。

 int sigaction(int signum, 
               const struct sigaction *act,
               struct sigaction *oldact); 
/*
 功能:绑定信号和处理信号的函数指针(通过一个结
构体实现);
 函数参数 :
 signum— 指定需要捕获的信号, SIGKILL 和 SIGSTOP
不能指定;
 act— 指定处理捕获信号的动作;
 oldact— 存储旧的动作
*/
struct sigaction {                  
void (*sa_handler)(int);   // 函数指针
sigset_t sa_mask;  // 屏蔽的信号集
int sa_flags; // 标志
void (*sa_restorer)(void);  // 已废弃
}
/*
 sa_handler 指定信号的动作:
 使用默认动作时设置为 SIG_DFL ;
 忽略信号时设置为 SIG_IGN ;
 使用用户指定的处理函数时设置为相应处理函数。
*/
 void (*signal(int signum,void (*handler)(int)))(int);
/*
参数:
 signum— 指定需要捕获的信号 ,SIGKILL 和 SIGSTOP 不能
指定  
 handler— 指定信号处理函数  
 返回值:void (*)(int);若成功返回原来的信号处理配置,如出错则为
SIG_ERR
如果使用下面的typedef,则可使其简单一些:
typedef void Sigfunc(int);
然后,可将signal函数原型写成:
Sigfunc *signal(int,Sigfunc *);
*/

IPC

Linux进程间通信( InterProcess Communication ,IPC )信主要方式:

  • 管道及命名管道:管道可用于具有亲缘关系进程间的通信,命名管道允许无亲缘关系进程间的通信;

  • 消息队列:消息的链接表,克服了信号灯承载信息量少,管道只能承载无格式字节流及缓冲区大小受限等缺点;

  • 共享内存:使得多个进程可以访问同一块内存空间,速度最快,常与信号量结合实现进程间同步及互斥;

  • 信号灯(或信号量):主要作为进程间及同一进程不同线程之间的同步手段;

  • 套接字:因特网套接字主要面向不同主机进程间通信, UNIX 域套接字面向同一主机上进程间通信

OOB

带外数据 OOB ,即 Out-Of-Band data ,用于快速传递数据信息,让这些信息在正常的网
络数据前到达目的地.也被称为快速数据( expedited data ),拥有比一般数据高的优先级;

IO模型

Linux 系统四种主要 I/O 模型:

  • 阻塞式 I/O

套接字的默认模型, I/O 无法完成时进入阻塞(即睡眠); 优点:编程简单;进程阻塞期间不占用 CPU 时间,不影响其他进程的工作效率;

缺点:进程可能长时间睡眠,在此期间无法执行别的任务,自身效率不高;

  • 非阻塞式 I/O

I/O 无法完成时进程不进入睡眠状态,而是立即返回一个错误; 优点:进程可以执行后续代码,提高自身工作效率;
缺点:进程一直运行,占用大量 CPU 时间检测 I/O 操作是
否完成,影响其他进程运行效率;

  • 多路复用 I/O

将多个 I/O 通道合成一组,通过 select() 函数来监视一组 I/O 通道的状态; 任何一个通道就绪,进程被激活,进行下一步处理,否则一直阻塞在 select() 函数(也可以设置一段超时时间);

  • 信号驱动 I/O

当描述符可以进行 I/O 操作时,操作系统内核发出一个 SIGIO 信号(表 4-1 ),通知用户进程启动一个 I/O 操作;
其余时间,用户进程不阻塞,可以执行其他操作;
通常只在 UDP 协议下使用, TCP 套接字基本不使用。

你可能感兴趣的:(C/C++)