网络编程容易出错点-动手才知道

一般认为网络编程就是socket编程,实际上,socket编程并不只是满足网络间不同主机之间的通信,它也能实现同一台主机上不同进程间的通信需求。其体现在创建socket时的参数的不同:

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

对于网络间的通信,domain是AF_INET,其创建的socket需要通过IP和端口来进行标识;对于同一台主机的进程间通信,其domain是AF_UNIX,其创建的socket则通过文件名来标识。

对于网络编程初学者而言,网络编程容易出错的地方包括以下:

1. socket有三种类型,即创建socket接口的第二个参数,         SOCK_STREAM代表TCP,即代表该socket可以像文件字节流那样操作;SOCK_DGRAM代表UDP,其是数据报文方式,在内核中抽象为类似消息队列一样管理;SOCK_RAW是原始socket类型,PING命令就是使用这种类型。我们在创建socket的时候一定要注意这个参数的设置,否则会出现难以捉摸的场景。初学者往往在练习TCP编程后直接拷贝代码改为UDP进行操作,这时如果不改这个类型,那是不能调试成功的。

2. socket接口的传参问题,先来看以下接口:

l   int bind (int sockfd, struct sockaddr* addr, int addrLen);

l   int accept(int sockfd, struct sockaddr *addr, int *addrlen) ;

l   int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

l   ssize_t sendto(int socket, void *message, size_t length, int flags, struct sockaddr *dest_addr, int dest_len);

l   ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, int  *address_len);

1)  以上接口的最后一个参数都是前一个sockaddr类型参数的字节长度,sockaddr类型参数是一个结构体,包含IP地址和PORT端口。从中可以看到在addrlen在某些接口里面的传参是值传递(如bind,connect,sendto),而在accept和recvfrom接口是地址传递。按一般的思维,地址传递是为了在接口中去改变这个地址所在的值,但是,这里的addrlen在以上接口实现中都不能被改进,它是用来告诉接口前一个参数的字节长度,所以以上所有接口在调用前这个addrlen都要被初始化,如addrlen
= sizeof(struct sockaddr), 然后再根据接口的传递要求(值传递还是地址传递)进行传参。

2)  对于str uct sockaddr* addr这个参数,接口中全部都是地址传递,但意义并不一样。

Bind接口是服务端的socket绑定自己的地址(IP和port),所有addr应该是要先初始化再传参;

Connect接口是客户端的socket去连接服务端,而接口中的addr就是服务端的地址,因此连接前也要进行初始化;

Sendto是UDP通信模式时源端(客户端/服务器)向对端(服务器/客户端)发送内容,由于UDP在通信前并未建立起连接,所有每次发送内容都要明确指明对端的地址,因此这个接口的addr也要先初始化再调用。

Accept是TCP模式中处于监听状态的服务端socket去接受客户端的连接,而这个接口的addr就是在连接成功时由系统内核调用填上客户端的地址信息,因此这个addr并不需要初始化;特别地,如果服务端的用户不想知道客户端的详细地址信息,那就传一个NULL进去,表示不care这个信息。如果accpet接口里面不想知道客户端的地址,那它又怎么能把数据传递回给客户端,那是因为accept返回的新的通信socket对应的内核数据结构已经记录到客户端的信息了。前面说服务端传NULL进去不care这个信息,只是说用户不想得到这个信息去做某些事情,例如打印地址信息等等,但对于内核维护的socket对应的数据结构,它是一定会记录客户端的地址信息的。Socket提供一个接口:getpeername(conn_socket_fd,
struct sockaddr*),就能够获取对端的地址信息,其跟accept接口直接获得对方的地址是等价的。

Recvfrom是UDP模式接收内容的接口,由于UDP模式在通信前不建立连接,要想在收到信息后给对方发送消息,Recvfrom收到信息时就必须要知道对端的地址,addr就是为了获取对端的地址信息的。因此其在使用前不需要初始化。

特别地,这里所讲的初始化是指初始化为有意义的地址值,如包括实际的IP和端口。对于不需初始化的addr,其在传参前应该将内存清0。

3. send、recv是TCP模式的接口,由于TCP在通信前已经建立起连接,即底层的socket内核数据结构已经记录对端的地址和本段的地址信息,因此send和recv接口不需传递对端的地址信息。

sendto、recvfrom是UDP模式的接口,通信前没有建立连接,所以每次都要填上/获取对方的地址。

两种模式的发送和接收接口是一一对应的,不能混用。

由于TCP在底层数据结构中是抽象为文件字节流来操作,所以可以用一般的read和write操作,其跟recv和send是相对应的,实现同样的功能,理论上是可以相互替代的,但为了代码的可维护性,仍然要遵循对应原则。

4. connect的阻塞问题。默认创建的socket都是阻塞的,connect也不例外,但在实践的过程中发现connect有时能够立即返回失败,并不阻塞。其原因是:练习时往往是在虚拟机上开一个终端启动客户端程序,其程序中是在连接本机(127.0.0.1)的某个端口,服务端未启动时,即该端口并未处于监听状态,因此客户端的connect能够立即返回失败,即其底层能够立刻得到失败的回复。阻塞的意义是一直等到有意义的应答,失败同样是一种有意义的应答,而并不是说一定要等到服务器来连接它。如果客户端程序去connect一个不存在的IP或者是经过多层路由的IP,那数据包会因为超时(IP包有一个段是标记最大存在时间的,超过这个时间的数据包会被直接丢弃)而返回,这时就可以发现connect确实是阻塞的。

5. int listen (int sockfd, int backlog)
监听接口的第二个参数表示sockfd的监听队列里面最大能够存在的客户端连接数,有客户端来连接connect,那客户端的socket就会被添加到服务端的监听队列里面,accept接口则是服务端从监听队列里面摘走一个socket,表示接受这个客户端的请求。所以backlog代表服务端暂时未能处理(accept)而存在于监听队列的最大连接数。如果服务端能够及时处理accept,那任意个客户端都能被连接进来,当前前提是不超过linux系统的规定数。很多人以为backlog是指最大允许这么多个客户来跟服务端通信,实际上理解错误的。可以这样做实验,服务端listen之后死循环,不再accept,看这时有多少个连接能够connect成功。

特别地,不能在同一台机启动客户端和服务端,因为同一台机并没有进行三次握手,connect总是能够返回成功,不受限于backlog。

网络编程容易出错点-动手才知道

时间: 2024-11-07 02:38:22

网络编程容易出错点-动手才知道的相关文章

完毕port(CompletionPort)具体解释 - 手把手教你玩转网络编程系列之三

手把手叫你玩转网络编程系列之三    完毕port(Completion Port)具体解释                                                              ----- By PiggyXP(小猪) 前 言 本系列里完毕port的代码在两年前就已经写好了,可是因为许久没有写东西了,不知该怎样提笔,所以这篇文档总是在酝酿之中--酝酿了两年之后,最终决定開始动笔了,但愿还不算晚-.. 这篇文档我很具体而且图文并茂的介绍了关于网络编程模型中完毕

很全的linux网络编程技巧

注:作者王晓,本人认为总结得很好,故记之,绝无侵权之意. 1. LINUX网络编程基础知识 1 1.1. TCP/IP协议概述 1 1.2. OSI参考模型及TCP/IP参考模型 1 1.3. TCP协议 3 1.4. UDP协议 5 1.5. 协议的选择 6 2. 网络相关概念 6 2.1. socket概念 7 2.2. socket类型 8 2.3. socket信息数据结构 8 2.4. 数据存储优先顺序的转换 8 2.5. 地址格式转化 9 2.6. 名字地址转化 10 3. sock

第三章 网络编程

终于学到网络编程了! 先上图和程序: 这是今天写的TCP的实现 服务器和客户端分别在两台电脑 这是服务器图: 这是服务器程序: 1 #-*- coding:utf-8 -*- 2 from socket import * #导入socket所有属性 3 from time import ctime #导入ctime() 4 5 6 host = '' #HOST 变量为空,表示bind()函数可以绑定在所有有效的地址上. 7 port = 21000 #设置端口 8 bufsize = 1024

网络编程学习小结

几种网络编程方式: ISAPI.CGI.WinInet.Winsock 它们之间的差别: 1)  ISAPI主要是开发基于浏览器client与server端程序.效率比CGI方式高,并且也扩展了CGI没有的一些功能.(基于TCP/IP模型中的应用层) 2)  CGI主要是开发基于浏览器client与server端程序.(基于TCP/IP模型中的应用层) 3)  WinInet主要是开发client程序.(基于TCP/IP模型中的应用层) 4)  Winsock主要是基于socket来开发clie

【UNIX网络编程】FIFO

管道作为进程间通信的最古老方式,它的缺点是没有名字,因此只能用在有亲缘关系的父子进程之间.对于无亲缘关系的进程间,无法用管道进行通信.FIFO可以完成无亲缘关系的进程间的通信,FIFO也被称为命名管道.它是一种特殊类型的文件,在文件系统中以文件名的形式存在,但它的行为却和上面提到的管道类似. 创建命名管道有两种方法: 1.在命令行上执行命令:mkfifo filename 来创建FIFO. 2.使用mkfifo函数创建FIFO. #include <sys/stat.h> #include &

『Python』socket网络编程

Python3网络编程 '''无论是str2bytes或者是bytes2str其编码方式都是utf-8 str( ,encoding='utf-8') bytes( ,encoding='utf-8') 而在使用.encode('utf-8')时,虽然type类型是byte,但常常报错''' Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯. socket起源于UNIX,在Unix一切皆文

VC++6.0网络编程Socket编程(转)

从csdn上下载的该软件的教程: 作为一个初学者,深感Socket编程入门的困难,但当把一些问题弄懂之后,回过头来看以前遇到的一些问题,才发现 Socket编程其实并没有那么复杂.接下来我就把我遇到的一些困难讲述下,并补上解决的办法. 首先我们要想实现一个简单的点对点网络通信,就应该有一个客户和一个服务器 我们先来做客户端.先按照如下图所示建立好客户对话框模块: 首先创建一个基于MFC AppWizard[EXE] 工程,工程名为Socket__002 (这里是以我的工程名为准,你们也可以自己命

Unix网络编程(六)高级I/O技术之复用技术 select

I/O复用技术 本文将讨论网络编程中的高级I/O复用技术,将从下面几个方面进行展开: a. 什么是复用技术呢? b. 什么情况下需要使用复用技术呢? c. I/O的复用技术的工作原理是什么? d. select, poll and epoll的实现机制,以及他们之间的区别. 下面我们以一个背景问题来开始: 包括在以前的文章中我们讨论的案例都是阻塞式的I/O包括(fgetc/getc, fgets/gets),即当输入条件未满足时进程会阻塞直到满足之后进行读取,但是这样导致的一个 问题是如果此时进

socket 网络编程

一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机. 而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的. 目前较为流行的网络编程模型是客户机/服务器(C/S)结构.即通信双方一方作为服务器等待客户提出请求并予以响应.客户则