热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

专题15TCP套接字编程

概述存在三种套接字:流式套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)。TCP套接字工作流程:


      1. 概述


存在三种套接字:流式套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)

TCP套接字工作流程:

首先,服务器端启动进程,调用Socket创建一个基于TCP协议的流套接字描述符。

其次,服务进程调用bind命名套接字,将套接字描述符绑定到本地地址和本地端口上。

再次,服务器端调用listen,开始侦听客户端的Socket连接请求。

接下来,客户端创建套接字描述符,并且调用connect向服务器端提交连接请求。服务器端接收到客户端连接请求后,调用accept,接受并创建一个新的套接字描述符与客户端建立连接,然后原套接字描述符继续侦听客户端的连接请求。

客户端与服务器端新套接字进行数据传输,调用writesend向对方发送数据,调用readrecv接收数据。

在数据交流完毕后,双方调用close或者shutdown关闭套字。

      1. socket的创建


函数原型:

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

PS:在实际编程中,我们只使用AF_INET协议,如果需要与本地主机进程建立连接,只需把远程地址设定为127.0.0.1”即可。

创建套接实例:

    1. 创建AF_INET协议族上的流套接字描述符。


socket(AF_INET,SOCK_STREAM, 0)

socket(AF_INET,SOCK_STREAM, TCP)

    1. 创建AF_INET协议族上的数据报套接字描述符。


socket(AF_INET,SOCK_DGRAM, 0)

socket(AF_INET,SOCK_DGRAM, UDP)

      1. socket的命名


函数bind命名一个套接字,它为该套接字描述符分配一个半相关属性,其原型如下:

intbind(int s, const struct sockaddr* name, int namelen);

套接字地址属性结构:

structsockaddr

{

u_shortsa_family; /*协议族*/

charsa_data[14] /*最多14字节的协议地址*/

}

每个协议族都定义了自己套接字属性结构,协议族AF_INET使用结构sockaddr_in描述套接字地址信息。

structsockaddr_in

{

shortsin_family; /*16位的地址协议族(AF_INET)*/

u_shortsin_port; /*16位的端口地址*/

structin_addr sin_addr; /*32位的IP地址*/

charsin_zero[8]; /*预留,保持sockaddr_in与结构sockaddr的长度相同*/

};

structin_addr

{

u_longs_addr;

}


IP地址转换:

unsignedlong inet_addr(char *ptr);

intinet_aton(char *ptr, struct in_addr *addrptr);

char*inet_ntoa(struct in_addr inaddr);//IP地址的整数形式转换为字符串格式输出


字节顺序转换:

u_longhtonl(u_long hostlong);

u_shorthtons(u_short hostshort);

u_longntohl(u_long netlong);

u_shortntohs(u_short netshort);

h”代表host”,代表主机字节序

n”代表network”,代表网络字节序。

l”代表long”,代表32位整数

s”代表short”,代表16位整数


例子:命名套接字描述符s的协议为AF_INET,地址由系统自动指定,端口号为1000.

structsockaddr_in sockaddr1; /*申请地址结构空间*/

memset(&sockaddr,0, sizeof(sockaddr)); /*清空空间*/

sockaddr1.sin_family= AF_INET; /*指定为AF_INET协议族*/

sockaddr1.sin_addr.s_addr= htonl(INADDR_ANY); /*任何IP地址*/

sockaddr1.sin_port= htons(10000); /*端口号*/

bind(s,(struct sockaddr *)&sockaddr1, sizeof(sockaddr1));


      1. socket的侦听


函数原型:

intlisten(int s, int backlog);

PS:listen仅在流套接字中使用。上述参数分别表示套接字描述符和套接字接受连接的最大数目。

例:服务器端套接字s进入侦听,最多只能接收客户端的10条申请。

listen(s,10);

/*TCP服务器端套接字准备函数*/

intCreateSock(int *pSock, int nPort, int nMax)

{

structsockaddr_in addrin;

structsockaddr *paddr = (struct sockaddr*)&addrin;

ASSERT(pSock!= NULL && nPort > 0 && nMax > 0);

memset(&addrin,0, sizeof(addrin));


addrin.sin_family= AF_INET;

addrin.sin_addr.s_addr= htonl(INADDR_ANY);

addrin.sin_port= htons(nPort);


ASSERT((*pSock=socket(AF_INET,SOCK_STREAM,0)) > 0);

if(VERIFY(bind(*pSock,paddr,sizeof(addrin))>= 0)&& VERIFY(listen(*pSock,nMax) >=0))

return0;

VERIFY(close(*pSock)== 0);

return1;

}

      1. Socket的连接处理


函数原型:

intaccept(int s, struct sockaddr *addr, int *addrlen);

/*TCP服务器端接收连接函数*/

intAcceptSock(int *pSock, int nSock)

{

structsockaddr_in addrin;

intlSize;

ASSERT(pSock!= NULL && nSock > 0);

while(1)

{

lSize= sizeof(addrin);

memset(&addrin,0, sizeof(addrin));

if((*pSock= accept(nSock,(structsockaddr*)&addrin,&lSize))> 0)

return0;

elseif(errno == EINTR)

continue;

else

ASSERT(0);

}

}

      1. socket的关闭


函数原型:

intshutdown(int s, int how);

函数shutdown可以全部关闭或者部分关闭套接字描述符s的连接,参数how指定了套接字关闭的方式,取值含义如下:


How取值

描述

0

套接字不可读,系统将自动丢弃接收到的数据和留在缓冲区中的数据,进程不能再从套接字中接收通信数据

1

套接字不可写,系统将写缓冲区的数据发送完毕后关闭套接字写操作,进程不能再从套接字中发送通信数据

2

彻底关闭套接字的连接


shutdown是强制性关闭全部套接字连接,而函数close只将套接字访问计数器减1,当且仅当计数器值为0时,系统才真正地关闭套接字通信。

PS:利用close的这个特性,可以建立Socket通信的服务器端的并发管理:父进程首先创建侦听套接字,一旦接收到连接请求则创建新的套接字与客户连接,再fork子进程,随后父进程调用close关闭新创建的套接字后继续侦听,子进程则调用close关闭侦听套接字,全权负责与客户端的通信。

      1. Socket的连接申请


函数原型:

intconnect(int s, const struct sockaddr *name, int namelen);

/*********TCP客户端函数**********/

intConnectSock(int *pSock, int nPort, char *pAddr)

{

structsockaddr_in addrin;

longlAddr;

intnSock;

ASSERT(pSock!= NULL && nPort > 0 && pAddr != NULL);

/***创建TCP套接字描述符***/

ASSERT((nSock= socket(AF_INET, SOCK_STREAM, p)) > 0);

/***协议地址组包*****/

memset(&addrin,0, sizeof(addrin));

addrin.sin_family= AF_INET;

addrin.sin_addr.s_addr= inet_addr(pAddr);

addrin.sin_port= htons(nPort);

if(VERIFY(connect(nSock,(struct sockaddr *)&addrin, sizeof(addrin)) > = 0))

{

/***连接成功,返回套接字描述符***/

*pSock= nSock;

return0;

}

close(nSock);

return1;

}

      1. TCP数据的发送


套接字一旦连接上,就可以发送数据。

函数原型:

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

flags标志:

MSG_OOB:发送外带数据

MSG_DONTROUTE:通知远程IP就在本地局域网内,消息中不加入跌幅消息

      1. TCP数据的接收


函数原型:

intrecv(int s, void *buf, int len, int flags);

flags标志:

MSG_OOB:接收外带数据

MSG_PEEK:以窥视方式接收数据,即只接收而不从缓冲区中删除数据,下一次调用recvread仍然可以接收这些数据

MSG_WAITALL:函数阻塞直到读取len字节数为止。不过本地标志并并非完全阻塞,当进程接收到信号,套接字出错、连接中断或指定了MSG_PEEK等情况出现时函数仍然会提前返回

      1. 其他一些相关函数


structhostent *gethostbyname(const char *name);

structhostent *gethostbyaddr(const char *addr, int len, int type);

voidherror(const char *string);//不能使用perror

structservent *getservbyname(const char *name, const char *proto);

structservent *getservbyport(int port, const char *proto);

intgetsockname(int s, struct sockaddr *name, int *namelen);

intgetpeername(int s, struct sockaddr *name, int *namelen);

      1. 套接字选项


函数原型:

intgetsockopt(int s, int level, int optname, void *optval, int *optlen);

intsetsockopt(int s, int level, int optname, const void *optval, intoptlen);

相关选项说明:

  1. SO_DEBUG

本选项只支持TCP协议,当选项打开时,系统内核跟踪套接字发送和接收的全部TCP数据记录。


  1. SO_REUSEADDRSO_REUSEPORT

当尝试将一个套接绑定到某个端口时,如果该端口已经被占用了,一般情况下,绑定会失败,如果设置了此选项,系统能够让套接字bind到正在使用的地址或端口上。


  1. SO_KEEPALIVE

本选项是周期性地测试套接字连接是否依然存在。

  1. SO_DONTROUT

本选项控制发送消息是否越过协议的路由机制,打开时套接字将绕过协议的路由机制发送的数据。

  1. SO_LINGER

当缓冲区中还有数据尚未发送时,套接字的默认关闭机制是系统将这些数据立即发送对方,但不等待对方确认接收马上关闭套接字。SO_LINGER可以改变这个设置,它的数据传递通过结构linger完成。linger的结构定义如下:

structlinger

{

intl_onoff;//选项开关

intl_linger;//延时时间

}

结构linger成员取值含义如下:

L_onoff

L_linger

关闭机制

0

忽略

默认关闭机制

0

0

直接丢弃缓冲区,立即关关套接字

0

0

等待缓冲区数据发送完毕或者延时l_linger秒后关闭套接字。如果套接字非阻塞选项打开,则立即关闭



  1. SO_BROADCAST

控制能否发送套接字数据广播,只用于数据报套接字和支持广播信息的网络上。

  1. SO_OOBINLINE

此选项打开时允许外带数据留在输入队列中,此时函数readrecv可以在不指明MSG_OOB标志的情况下读取外带数据。

  1. SO_SNDBUFSO_RCVBUF

每个套接都有一个发送缓冲区和接收缓冲区,这两个缓冲区由底层的协议使用,选项SO_SNDBUF可以改变发送缓冲区的大小,选项SO_RCVBUF可以改变接收缓冲区的大小。客户端必须在connect前设置SO_RCVBUF选项,服务器端必须在listen前设置SO_RCVBUF选项,才可以有效地更改缓冲区的容量大小。

  1. SO_SNDLOWATSO_RCVLOWAT

这两个选项分别可以改变最小发送数据量和最小接收数据量。

  1. SO_SNDTIMEOSO_RCVTIMEO

这两个选项用于设置发送数据和接收数据设置一个超时时间。超时时间采用结构timeval描述。

  1. SO_TYPE

调用本选项可以获取套接字的类型,返回值为SOCK_STREAMSOCK_DGRAMSOCK_RAW等,此选项不能用于setsockopt中。

  1. SO_ERROR

调用本选项可以获取并且清除套接字错误,不能应用于函数setsockopt中。





推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 本文介绍了在CentOS 7.x上进行端口映射配置的方法,通过修改内核和配置防火墙实现端口映射。作者分享了自己使用华为服务器进行端口映射的经验,发现网速比直连还快且稳定。详细的配置过程包括开启系统路由模式功能、设置IP地址伪装、设置端口映射等。同时,还介绍了如何监听本地端口的tcp请求,以及删除规则和开放的端口的方法。 ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了如何找到并终止在8080端口上运行的进程的方法,通过使用终端命令lsof -i :8080可以获取在该端口上运行的所有进程的输出,并使用kill命令终止指定进程的运行。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了如何使用iptables添加非对称的NAT规则段,以实现内网穿透和端口转发的功能。通过查阅相关文章,得出了解决方案,即当匹配的端口在映射端口的区间内时,可以成功进行端口转发。详细的操作步骤和命令示例也在文章中给出。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 概述H.323是由ITU制定的通信控制协议,用于在分组交换网中提供多媒体业务。呼叫控制是其中的重要组成部分,它可用来建立点到点的媒体会话和多点间媒体会议 ... [详细]
author-avatar
婕小米
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有