网络编程套接字【socket】

在学习Linux系统编程的时候,进程间的通信方式包括——管道、消息队列、共享内存、信号量等方式。但是这些通信方式都村子一定的缺陷——都是在同一个机器上的进程间的通信。为了让不同机器上的进程之间相互通信,Linux网络编程便可解决。linux系统支持套接字接口,可以通过与使用管道类似的方法来使用套接字,但套接字还包含了计算机网络的通信。

Linux网络编程---套接字。

套接字(socket)是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。套接字的创建和使用与管道是有区别的,因为套接字明确的将客户和服务器区分开来。套接字机制可以实现将多个客户连接到一个服务器。

知识储配:

端口号(port):是传输层协议的内容。

1.端口号是一个2字节16位的整数;

2.端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪个进程来处理;

3.IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;

4.一个端口号通常只能被一个进程占用。(特例:父子进程)

5.一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。

6.端口号与主机中表示进程的pid不同,一个是网络上的,后者是操作系统内核中分配的。

字节序:

字节序分为主机字节序和网络字节序:

网络字节序:是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian(大端)排序方式。

主机字节序:不同的主机具有不同的字节序:可以同过以下方法测试自己系统的主机字节序。

//小端字节序:小小小--- 低位放在低地址处为小端
int main()
{
  union tst
  {
    short a;
    char b;
  }tst;
  tst.a = 0x0102;
  if(tst.b == 1)
  {
    printf("big endian\n");
  }
  else{
    printf("little endian\n");
  }
  return 0;
}

当格式化的数据在两个使用不同字节序的主机之间直接传递时,接收端必然会错误的解释数据,因此,发送端在发送的时候讲数据转化成网络字节序再发送,接收方将数据转成字节的主机字节序接收和解释。需要指出的是,即使在同一台机器的两个进程,有时也要考虑字节序的问题。Linux提供了4个函数来完成主机字节序和网络字节序之间的转换。

  uint32_t htonl(uint32_t hostlong);      
  uint16_t htons(uint16_t hostshort);            均返回网络字节序的值
  uint32_t ntohl(uint32_t netlong);
  uint16_t ntohs(uint16_t netshort);             均返回主机字节序的值    

h表示主机(host)字节序,n表示网络(network)字节序,l表示长(long)整数

s表示短(short)整数。

地址转换函数:

int inet_aton(const char *cp, struct in_addr *inp);   
in_addr_t inet_addr(const char *cp);                
char *inet_ntoa(struct in_addr in);                  

 

inet_addr将点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址

inet_aton:同inet_addr功能相同,但是将转化结果存储在参数inp指向的地址结构中。成功返回1,失败返回0.   

inet_ntoa:将网络字节序整数表示成IPv4地址转化为用点分十进制字符串表示的IPv4地址,但该函数内部用一个静态变量存储转化结果,函数的返回值指向该竞态内存,因此该函数是不可重入的。在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题。

从TCP了解套接字接口函数:

1.套接字描述符:socket打开一个网络通讯端口,如果成功的话,就像open一样返回一个文件描述符。应用程序可以像读写文件一样用read/write在网络上收发数据。

int socket(int domain, int type, int protocol);

domain(域):确定通信的特性,包括地址格式,各个域有自己的格式表示地址,表示各个域的常数都以AF_开头,指地址族。

type:确定套接字类型,进一步确认通信特征。

protocol:通常为0,表示按给定的域和套接字类型选择默认协议。

返回值:

 成功: 非负的文件描述符 。 失败:-1

2.将套接字与地址绑定(主要针对服务器,需要绑定一个众所周知的端口,而不能让内核去临时的分配):

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

对于绑定的地址的限制:

1.所运行机器上的指定的地址必须有效

2.地址必须和创建套接字时的地址族所支持的格式相匹配。

3.端口号必须大于1024,除非该进程具有超级特权

4.一般只有套接字端点能够与地址绑定

socket:要绑定的套接字

address:一个通用的指针类型,可以接受多种协议的sockaddr结构体,而他们的长度各不相同,所以需要第三个参数指定结构体的长度。

address_len:地址指针的长度

返回值:成功返回0,出错返回-1

3.监听socket

int listen(int s, int backlog);

返回值:成功返回0,出错返回-1

listenb仅被TCP服务器调用时,它作了两件事:

1.当函数socket创建一个套接字接口时,它被假设为一个主动地套接口,

当有客户端通过connect发起链接这个端口时,listen函数指示内核接受

指向此套接口的请求。调用listen函数将导致套接口从CLOSED状态转为LISTEN状态。

2.函数的第二个参数规定了内核为此套接口排队的最大 链接个数。(典型值为5)

backlog理解(这里可以类比的记忆海底捞吃饭的例子):对于给定的监听套接字,内核维护了两个队列:

未完成链接队列:已由客户端发出并到达服务器,服务器正在等待完成相应的TCP三次握手过程。这些套接口都处于SYN_RCVD状态

已完成连接队列:唯为每个已完成TCP三次握手的客户端请求分配一个条目,这些套接口都处于ESTABLISTED状态。

内核版本2.2之后backlog只表示处于完全连接状态的socket的上限。

4.接受链接

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);

accept由TCP服务器调用,从已完成连接队列头返回下一个已完成的链接,若已完成链接队列为空,则accept会阻塞直到一个请求到来,如若sockfd处于非阻塞模式,accept会返回-1并将errno设置为EAGAIN或EWOULDBLOCK。

返回值:成功返回文件描述符,出错返回-1

addr和addrlen用来返回对端的进程(客户端)的协议地址,addrlen是结果参数调用前,我们将由*addrlen所指的整数值置为由cliaddr所指向的套接口地址结构的长度,返回时,这个整数值即为有内核存在此套接口地址结构内的准确字节数。

1>三次握手完成后,服务器调用accept()接受链接

2>addr是一个传出参数,accept()返回时传出客户端的地址和端口号;

3>如果给addr 参数传NULL,表示不关心客户端的地址;

4>addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度

5.客户端链接函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

返回值:成功0,出错-1

sockfd:由socket函数返回的套接口描述字

addr:指向套接口地址结构的指针

addrlen:地址结构的大小

客户端在调用connect前不必非得调用函数bind,因为如若必要的话内核会选择源IP地址和一个临时的端口。如果是TCP套接口的话,函数connect激发TCP的三次握手过程。

客户端和服务器建立连接的函数介绍完毕,下面在介绍几个在数据传输时用到的函数。

如果连接建立成功,那么我们就可以使用read/write来通过套接字进行通信。但是这两个函数的功能有些单一,如果想指定选项从多个客户端接收数据包,或发送外带数据,read/write就不能满足我们的要求。

发送数据:send, sendto, 和 sendmsg 用于向另一个套接字传递消息.  Send 仅仅用于连接套接字,而 sendto 和sendmsg 可用于任何情况下.它们的返回值都是成功返回发送的字节数,出错返回-1。

int send(int s, const void *msg, size_t len, int flags);

send和write很像,但是可以指定标志位来改变处理传输数据的方式。
int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

send和send类似,区别在于sendto允许在无连接的套接字上指定一个目标地址。

对于面向链接的套接字,目标地址是忽略的,因为目标地址蕴含在连接中,对于无连接的套接字,不能使用send除非在调用connect时预先设定了目标地址,或者采用sendto来提供另一种发送报文的方式。
int sendmsg(int s, const struct msghdr *msg, int flags);

sendmsg提供了一个结构体来存储要发送的数据。

接收数据:返回值都是成功返回以字节计数的消息长度,若无可用消息或对方已经按序结束则返回0,出错返回-1

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

        recv和read很像,但是可以指定flags选项来控制如何接收数据。如果发送者已经调用shutdown来结束传输,或者网络协议支持默认的顺序关闭并且发送端已经关闭,那么当所有的数据接收完毕后,recv返回0.

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

       如果需要定位数据的发送者,可以使用recvfrom来得到数据发送者的源地址,如果addr非空,它将包含数据发送者的套接字端点地址。当调用recvfrom时,需要设置addrlen参数指向一个包含addr所指的套接字缓冲区字节大小的整数。返回时,改正数设为改地址的实际字节大小因为可以获得发送者的地址,recvfrom通常用于无连接套接字。

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

为了将收到的数据送人多个缓冲区,或者想接受辅助数据可以使用recvmsg。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值