如何关闭atcp套接字编程实例不会内存泄露

【图文】基于 TCP 套接字通信_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
基于 TCP 套接字通信
登录百度文库,专享文档复制特权,财富值每天免费拿!
你可能喜欢博客分类:
为什么TCP不可靠
“TCP在ip(7)层(ipv4 和 ipv6)之上建立了一个连接两个套接字之间的,可靠的,面向流的,全双工连接。TCP保证数据按序到达并重传丢失的数据包。它生成并检查每一个数据包的校验和来捕获传输错误。“
sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &remote, sizeof(remote));
write(sock, buffer, 1000000);
// returns 1000000
close(sock);
int sock = socket(AF_INET, SOCK_STREAM, 0);
bind(sock, &local, sizeof(local));
listen(sock, 128);
int client=accept(sock, &local, locallen);
write(client, "220 Welcome\r\n", 13);
int bytesRead=0,
res = read(client, buffer, 4096);
if(res & 0)
perror("read");
bytesRead +=
printf("%d\n", bytesRead);
A) 1000000
B) 小于1000000的某个数字
C) 一条错误消息
D) 以上都有可能
“启用时,在close(2)或shutdown(2)直到所有排队的消息都成功发送或超过逗留时间时才会返回。否则,调用立即返回关闭将在后台完成。当套接字由exit(2)关闭时,它将总在后台逗留。”
“主机可以实现”半双工“TCP关闭序列,使得调用close的应用程序,不能继续从连接读取数据。如果这样的主机在读取TCP中挂起的数据时调用 close,或者在调用close以后又有新数据大大是,TCP应该发送一个RST来表明数据已丢失。”
sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &remote, sizeof(remote));
write(sock, buffer, 1000000);
// returns 1000000
shutdown(sock, SHUT_WR);
res=read(sock, buffer, 4000);
if(res & 0) {
perror("reading");
close(sock);
watter1985
浏览: 42615 次
来自: 深圳
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'您所在的位置: &
基本 TCP 套接字编程讲解
基本 TCP 套接字编程讲解
逆风微积分
基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept 函数。
基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用
bind、listen 和accept 函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown
函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。以下是基于 TCP 套接字编程的流程图:
498)this.width=498;' onmousewheel = 'javascript:return big(this)' height="747" alt="" src="http://img.blog.csdn.net/03733?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbmhhbnpodW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" width="510" />
socket 函数
套接字是通信端点的抽象,实现端对端之间的通信。与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。任何套接字编程都必须调用socket
函数获得套接字描述符,这样才能对套接字进行操作。以下是该函数的描述:
/*&套接字&*/&&&&&&/*&&&*&函数功能:创建套接字描述符;&&&*&返回值:若成功则返回套接字非负描述符,若出错返回-1;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&&&&int&socket(int&family,&int&type,&int&protocol);&&&/*&&&*&说明:&&&*&socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;&&&*&family&表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:&&&*&(1)AF_INET&&&&&&&&&IPv4因特网域&&&*&(2)AF_INET6&&&&&&&&IPv6因特网域&&&*&(3)AF_UNIX&&&&&&&&&Unix域&&&*&(4)AF_ROUTE&&&&&&&&路由套接字&&&*&(5)AF_KEY&&&&&&&&&&密钥套接字&&&*&(6)AF_UNSPEC&&&&&&&未指定&&&*&&&*&type确定socket的类型,常用类型如下:&&&*&(1)SOCK_STREAM&&&&&有序、可靠、双向的面向连接字节流套接字&&&*&(2)SOCK_DGRAM&&&&&&长度固定的、无连接的不可靠数据报套接字&&&*&(3)SOCK_RAW&&&&&&&&原始套接字&&&*&(4)SOCK_SEQPACKET&&长度固定、有序、可靠的面向连接的有序分组套接字&&&*&&&*&protocol指定协议,常用取值如下:&&&*&(1)0&&&&&&&&&&&&&&&选择type类型对应的默认协议&&&*&(2)IPPROTO_TCP&&&&&TCP传输协议&&&*&(3)IPPROTO_UDP&&&&&UDP传输协议&&&*&(4)IPPROTO_SCTP&&&&SCTP传输协议&&&*&(5)IPPROTO_TIPC&&&&TIPC传输协议&&&*&&&*/&&&
connect 函数
在处理面向连接的网络服务时,例如 TCP ,交换数据之前必须在请求的进程套接字和提供服务的进程套接字之间建立连接。TCP 客户端可以调用函数connect
来建立与 TCP 服务器端的一个连接。该函数的描述如下:
/*&&&*&函数功能:建立连接,即客户端使用该函数来建立与服务器的连接;&&&*&返回值:若成功则返回0,出错则返回-1;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&&&&int&connect(int&sockfd,&const&struct&sockaddr&*servaddr,&socklen_t&addrlen);&&&/*&&&*&说明:&&&*&sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符;&&&*&servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址;&&&*&addrlen是目的套接字地址的大小;&&&*&&&*&如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号;&&&*/&&&
TCP 客户端在调用函数 connect 前不必非得调用 bind 函数,因为内核会确定源 IP 地址,并选择一个临时端口作为源端口号。若 TCP
套接字调用connect 函数将建立 TCP 连接(执行三次握手),而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
若 TCP 客户端没有收到 SYN 报文段的响应,则返回 ETIMEOUT 错误;
若客户端的 SYN 报文段的响应是 RST (表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。只是一种硬错误,客户端一接收到
RST 就立即返回ECONNERFUSED 错误;
RST 是 TCP 在发生错误时发送的一种 TCP 报文段。产生 RST 的三个条件时:
目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器;
TCP 想取消一个已有连接;
TCP 接收到一个不存在的连接上的报文段;
若客户端发出的 SYN 在中某个路由器上引发一个目的地不可达的 ICMP 错误,这是一个软错误。客户端主机内核保存该消息,并在一定的时间间隔继续发送
SYN (即重发)。在某规定的时间后仍未收到响应,则把保存的消息(即 ICMP 错误)作为EHOSTUNREACH 或ENETUNREACH
错误返回给进行。
调用函数 socket 创建套接字描述符时,该套接字描述符是存储在它的协议族空间中,没有具体的地址,要使它与一个地址相关联,可以调用函数bind
使其与地址绑定。客户端的套接字关联的地址一般可由系统默认分配,因此不需要指定具体的地址。若要为服务器端套接字绑定地址,可以通过调用函数 bind
将套接字绑定到一个地址。下面是该函数的描述:
/*&套接字的基本操作&*/&&&&&&/*&&&*&函数功能:将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号;&&&*&返回值:若成功则返回0,若出错则返回-1;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&int&bind(int&sockfd,&const&struct&sockaddr&*addr,&socklen_t&addrlen);&&&/*&&&*&说明:&&&*&sockfd&为套接字描述符;&&&*&addr是一个指向特定协议地址结构的指针;&&&*&addrlen是地址结构的长度;&&&*/&&&
对于 TCP 协议,调用 bind 函数可以指定一个端口号,或指定一个 IP 地址,也可以两者都指定,还可以都不指定。若 TCP
客户端或服务器端不调用bind 函数绑定一个端口号,当调用connect 或 listen 函数时,内核会为相应的套接字选择一个临时端口号。一般 TCP
客户端使用内核为其选择一个临时的端口号,而服务器端通过调用bind 函数将端口号与相应的套接字绑定。进程可以把一个特定的 IP 地址捆绑到它的套接字上,但是这个
IP 地址必须属于其所在主机的网络接口之一。对于 TCP 客户端,这就为在套接字上发送的 IP 数据报指派了源 IP 地址。对于 TCP
服务器端,这就限定该套接字只接收那些目的地为这个 IP 地址的客户端连接。TCP 客户端一般不把 IP
地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源 IP 地址,而所用外出接口则取决于到达服务器端所需的路径。若 TCP 服务器端没有把
IP 地址捆绑到它的套接字上,内核就把客户端发送的 SYN 的目的 IP 地址作为服务器端的源 IP 地址。
在地址使用方面有下面一些限制:
在进程所运行的机器上,指定的地址必须有效,不能指定其他机器的地址;
地址必须和创建套接字时的地址族所支持的格式相匹配;
端口号必须不小于1024,除非该进程具有相应的特权(超级用户);
一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定;
listen 函数
在编写服务器程序时需要使用监听函数 listen
。服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。listen
函数描述如下:
/*&&&*&函数功能:接收连接请求;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&&&&int&listen(int&sockfd,&int&backlog);//若成功则返回0,若出错则返回-1;&&&/*&&&*&sockfd是套接字描述符;&&&*&backlog是该进程所要入队请求的最大请求数量;&&&*/&&&
listen 函数仅由 TCP 服务器调用,它有以下两种作用:
当 socket 函数创建一个套接字时,若它被假设为一个主动套接字,即它是一个将调用connect 发起连接的客户端套接字。listen
函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求;
listen 函数的第二个参数规定内核应该为相应套接字排队的最大连接个数;
listen 函数一般应该在调用socket 和bind 这两个函数之后,并在调用 accept 函数之前调用。
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接字处于
SYN_REVD 状态;
已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态;
accept 函数
accept 函数由 TCP
服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示
TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。该函数描述如下:
/*&函数功能:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠;&&&*&函数原型:&&&*/&&&int&accept(int&sockfd,&struct&sockaddr&*cliaddr,&socklen_t&*addrlen);//返回值:若成功返回套接字描述符,出错返回-1;&&&/*&&&*&说明:&&&*&参数&cliaddr&和&addrlen&用来返回已连接的对端(客户端)的协议地址;&&&*&&&*&该函数返回套接字描述符,该描述符连接到调用connect函数的客户端;&&&*&这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接,&&&*&而是继续保持可用状态并接受其他连接请求;&&&*&若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址,&&&*&并且将addrlen设为指向代表这个缓冲区大小的整数指针;&&&*&accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小;&&&*&&&*&若没有连接请求等待处理,accept会阻塞直到一个请求到来;&&
fork 和 exec 函数
/*&函数功能:创建子进程;&&&*&返回值:&&&*&(1)在子进程中,返回0;&&&*&(2)在父进程中,返回新创建子进程的进程ID;&&&*&(3)若出错,则范回-1;&&&*&函数原型:&&&*/&&&#include&&&&pid_t&fork(void);&&&/*&说明:&&&*&该函数调用一次若成功则返回两个值:&&&*&在调用进程(即父进程)中,返回新创建进程(即子进程)的进程ID;&&&*&在子进程返回值是0;&&&*&因此,可以根据返回值判断进程是子进程还是父进程;&&&*/&&&&&&/*&exec&序列函数&*/&&&&&&/*&&&*&函数功能:把当前进程替换为一个新的进程,新进程与原进程ID相同;&&&*&返回值:若出错则返回-1,若成功则不返回;&&&*&函数原型:&&&*/&&&#include&&&&int&execl(const&char&*pathname,&const&char&*arg,&...);&&&int&execv(const&char&*pathnam,&char&*const&argv[]);&&&int&execle(const&char&*pathname,&const&char&*arg,&...&,&char&*const&envp[]);&&&int&execve(const&char&*pathnam,&char&*const&argv[],&char&*const&envp[]);&&&int&execlp(const&char&*filename,&const&char&*arg,&...);&&&int&execvp(const&char&*filename,&char&*const&argv[]);&&&/*&&6&个函数的区别如下:&&&*&(1)待执行的程序文件是&文件名&还是由&路径名&指定;&&&*&(2)新程序的参数是&一一列出&还是由一个&指针数组&来引用;&&&*&(3)把调用进程的环境传递给新程序&还是&给新程序指定新的环境;&&&*/&&&
exec 6个函数在函数名和使用语法的规则上都有细微的区别,下面就从可执行文件查找方式、参数传递方式及环境变量这几个方面进行比较。
查找方式:前4个函数的查找方式都是完整的文件目录路径 pathname ,而最后两个函数(也就是以p结尾的两个函数)可以只给出文件名
filename,系统就会自动按照环境变量 &$PATH& 所指定的路径进行查找。
参数传递方式:exec
序列函数的参数传递有两种方式:一种是逐个列举的方式,而另一种则是将所有参数整体构造指针数组传递。在这里是以函数名的第5位字母来区分的,字母为
&l&(list)的表示逐个列举参数的方式,其语法为 const char *字母为
&v&(vertor)的表示将所有参数整体构造指针数组传递,其语法为 char *const argv[]。读者可以观察
execl()、execle()、execlp() 的语法与 execv()、execve()、execvp()
的区别。这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括该可执行程序命令本身)。要注意的是,这些参数必须以NULL结束。
环境变量:exec 序列函数可以默认系统的环境变量,也可以传入指定的环境变量。这里以 &e&(environment)结尾的两个函数 execle() 和
execve() 就可以在 envp[] 中指定当前进程所使用的环境变量。
&表&1&exec&序列函数的总结&&&&&&&前4位&统一为:exec&&&&&&&第5位&l:参数传递为逐个列举方式&&&&execl、execle、execlp&&&&&&&&v:参数传递为构造指针数组方式&&&&&execv、execve、execvp&&&第6位&e:可传递新进程环境变量&&&&&execle、execve&&&&&&&&p:可执行文件查找方式为文件名&&&&&execlp、execvp&&&
其关系如下图:
498)this.width=498;' onmousewheel = 'javascript:return big(this)' height="173" alt="" src="http://img.blog.csdn.net/32988?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbmhhbnpodW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" width="409" />
并发服务器
当要求一个服务器同时为多个客户服务时,需要并发服务器。TCP 并发服务器,它们为每个待处理的客户端连接调用 fork
函数派生一个子进程。当一个连接建立时,accept 返回,服务器接着调用 fork
函数,然后由子进程服务客户端,父进程则等待另一个连接,此时,父进程必须关闭已连接套接字。
close 和 shutdown 函数
当要关闭套接字时,可使用 close 和 shutdown 函数,其描述如下:
/*&函数功能:关闭套接字,若是在&TCP&协议中,并终止&TCP&连接;&&&*&返回值:若成功则返回0,若出错则返回-1;&&&*&函数原型:&&&*/&&&#include&&&&int&close(int&sockfd);&&&&&&/*&&&*&函数功能:关闭套接字上的输入或输出;&&&*&返回值:若成功则返回0,若出错返回-1;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&int&shutdown(int&sockfd,&int&how);&&&/*&&&*&说明:&&&*&sockfd表示待操作的套接字描述符;&&&*&how表示具体操作,取值如下:&&&*&(1)SHUT_RD&&&&&关闭读端,即不能接收数据&&&*&(2)SHUT_WR&&&&&关闭写端,即不能发送数据&&&*&(3)SHUT_RDWR&&&关闭读、写端,即不能发送和接收数据&&&*&&&*/&&&
getsockname 和 getpeername 函数
为了获取已绑定到套接字的地址,我们可以调用函数 getsockname 来实现:
/*&&&*&函数功能:获取已绑定到一个套接字的地址;&&&*&返回值:若成功则返回0,若出错则返回-1;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&&&&int&getsockname(int&sockfd,&struct&sockaddr&*addr,&socklen_t&*alenp);&&&/*&&&*&说明:&&&*&调用该函数之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小;&&&*&返回时,该整数会被设置成返回地址的大小,如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错;&&&*/&&&/*&&&*&函数功能:获取套接字对方连接的地址;&&&*&返回值:若成功则返回0,若出错则返回-1;&&&*&函数原型:&&&*/&&&#include&/socket.h&&&&&&int&getpeername(int&sockfd,&struct&sockaddr&*addr,&socklen_t&*alenp);&&&/*&&&*&说明:&&&*&该函数除了返回对方的地址之外,其他功能和getsockname一样;&&&*/&&&
【编辑推荐】
【责任编辑: TEL:(010)】
关于的更多文章
TCP/IP(传输入控制地议/网际协议)是一种网络通信协议,它规范
随着云计算、物联网、大数据、移动互联网的大发展,你应该知道这些。
讲师: 289人学习过讲师: 1人学习过讲师: 3人学习过
东华网智自成立以来,推出了独具特色的自主研发系列产
在刚刚迈入2014年之际,锐捷网络提出了简网络 云体验
在思科中国20周年之际,2014思科合作与发展论坛暨思科
本书的第1版获得过“2006年度全行业优秀畅销品种奖”。全书共15章,分别介绍了网管员职责和应具备的工作习惯、共享上网与访问控
51CTO旗下网站第一部分:知识备忘
1、进程间通信方式
本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:
消息传递(管道、FIFO、消息队列)
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内存(匿名的和具名的)
远程过程调用(Solaris门和Sun RPC)
但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的&ip地址&可以唯一标识网络中的主机,而传输层的&协议+端口&可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX& BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说&一切皆socket&。
2、socket及接口函数
socket起源于Unix,而Unix/Linux基本哲学之一就是&一切皆文件&,都可以用&打开open && 读写write/read && 关闭close&模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),这些函数我们在后面进行介绍。
socket一词的起源
在组网领域的首次使用是在日发布的文献中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:&命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。&计算机历史博物馆补充道:&这比BSD的套接字接口定义早了大约12年。&
socket()函数
1、int socket(int domain, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
2、bind()函数
正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数的三个参数分别为:
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addr:一个const&struct&sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
struct sockaddr_in {
sa_family_t
sin_ /* address family: AF_INET */
/* port in network byte order */
struct in_addr sin_
/* internet address */
/* Internet address. */
struct in_addr {
/* address in network byte order */
ipv6对应的是:&
struct sockaddr_in6 {
sa_family_t
/* AF_INET6 */
/* port number */
sin6_ /* IPv6 flow information */
struct in6_addr sin6_
/* IPv6 address */
sin6_scope_ /* Scope ID (new in 2.4) */
struct in6_addr {
unsigned char
s6_addr[16];
/* IPv6 address */
Unix域对应的是:&
#define UNIX_PATH_MAX
struct sockaddr_un {
sa_family_t sun_
/* AF_UNIX */
sun_path[UNIX_PATH_MAX];
/* pathname */
addrlen:对应的是地址的长度。
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
网络字节序与主机字节序
主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:
  a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。
3、listen()、connect()函数
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
4、accept()函数
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct&sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
5、read()、write()等函数
万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:
#include &unistd.h&
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include &sys/types.h&
#include &sys/socket.h&
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, 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 recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
其它的我就不一一介绍这几对I/O函数了,具体参见man文档或者baidu、Google,下面的例子中将使用到send/recv。
6、close()函数
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
#include &unistd.h&
int close(int fd);
close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
第二部分:代码展示
1、简单的单tcp通信实例
1 /************************************************************************/
2 /* 服务端源代码 tcpserver.cpp
3 /************************************************************************/
4 #include &stdio.h&
5 #include&stdlib.h&
6 #include &iostream&
7 #include&string.h&
8 #include &sys/socket.h&
9 #include&sys/types.h&
10 #include&unistd.h&
11 #include&netinet/in.h&
12 #include&errno.h&
13 #define
14 #define
LENTH 1024
15 int main(int argc,char **argv)
int server_sockfd, client_
struct sockaddr_in server_
struct sockaddr_in client_
char recvline[LENTH], sendline[LENTH];
server_sockfd = socket(AF_INET,SOCK_STREAM,0);
if (server_sockfd==-1)
perror("server_sockfd");
return -1;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
int bind_result = bind(server_sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if (bind_result==-1)
perror("bind");
return -1;
int listen_result = listen(server_sockfd,5);
if (listen_result==-1)
perror("listen");
return -1;
printf("listen the port");
addrlen = sizeof(struct sockaddr);
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_addr,(socklen_t*)&addrlen);
if (client_sockfd==-1)
perror("accept");
return -1;
printf("waiting for client...\n");
int recv_len = recv(client_sockfd,recvline,LENTH,0);
if (recv_len==-1)
perror("recv");
return -1;
printf("recv data is :%s\n",recvline);
printf("input your words:\n");
scanf("%s",sendline);
int send_len=send(client_sockfd,sendline,strlen(sendline),0);
if (send_len==-1)
perror("send");
return -1;
close(client_sockfd);
close(server_sockfd);
1 #include &iostream&
2 #include &stdio.h&
3 #include &stdlib.h&
4 #include &sys/socket.h&
5 #include &sys/types.h&
6 #include &arpa/inet.h&
7 #include &string.h&
8 #include &unistd.h&
9 #include &netinet/in.h&
10 #define
11 #define
LENTH 1024
12 int main(int argc,char **argv)
struct sockaddr_in addr_
char sendline[LENTH],recvline[LENTH];
sockfd = socket(AF_INET,SOCK_STREAM,0);
if (sockfd==-1)
perror("socket");
return -1;
memset(&addr_server,0,sizeof(addr_server));
addr_server.sin_family = AF_INET;
addr_server.sin_addr.s_addr = inet_addr("192.168.7.22");
addr_server.sin_port = htons(PORT);
int connect_result = connect(sockfd,(struct sockaddr*)&addr_server,sizeof(addr_server));
if (connect_result==-1)
perror("connect");
return -1;
printf("connect with server...\n");
printf("input your words:\n");
scanf("%s",&sendline);
int send_result=send(sockfd,sendline,sizeof(sendline),0);
if (send_result==-1)
perror("send");
return -1;
printf("waitint for server...\n");
int recv_result = recv(sockfd,recvline,sizeof(recvline),0);
if (recv_result==-1)
perror("recv");
return -1;
printf("recv data is:%s\n",recvline);
memset(recvline,0,sizeof(recvline));
效果展示:
参考博客:
注:有个不太合理的地方,判断错误返回前,应该先释放掉套接字,用close()函数。
阅读(...) 评论()

我要回帖

更多关于 tcp套接字 的文章

 

随机推荐