网络编程-connect函数

来源:互联网 发布:校园网络拓扑图及意图 编辑:程序博客网 时间:2024/06/05 03:05

(1)connect描述

定义函数:
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
connect函数通常用于客户端建立tcp连接。

 

参数:
sockfd:标识一个套接字。
serv_addr:套接字s想要连接的主机地址和端口号。
addrlen:name缓冲区的长度。

 

返回值:
成功则返回0,失败返回-1,错误原因存于errno中。

 

错误代码:
EBADF 参数sockfd 非合法socket处理代码
EFAULT 参数serv_addr指针指向无法存取的内存空间
ENOTSOCK 参数sockfd为一文件描述词,非socket。
EISCONN 参数sockfd的socket已是连线状态
ECONNREFUSED 连线要求被server端拒绝。
ETIMEDOUT 企图连线的操作超过限定时间仍未有响应。
ENETUNREACH 无法传送数据包至指定的主机。
EAFNOSUPPORT sockaddr结构的sa_family不正确。
EALREADY socket为不可阻塞且先前的连线操作还未完成。

 

(2)SOCKET中连接过程比较
      connect是套接字连接操作,connect操作之后代表对应的套接字已连接,UDP协议在创建套接字之后,可以同多个服务器端建立通信,而TCP协议只能与一个服务器端建立通信,TCP不允许目的地址是广播或多播地址,UDP允许。当然UDP协议也可以像TCP协议一样,通过connect来指定对方的ip地址、端口。
      UDP协议经过connect之后,在通过sendto来发送数据报时不需要指定目的地址、端口,如果指定了目的地址、端口,那么会返回错误。通过UDP协议可以给同一个套接字指定多次connect操作,而TCP协议不可以,TCP只能指定一次connect操作。UDP协议指定第二次connect操作之后会先断口第一次的连接,然后建立第二次的连接。


(3)客户端在建立同服务器端的连接过程
第一步都会通过socket建立连接套接字;
第二步通过bind来绑定本地地址、本地端口,当然绑定操作可以不用指定;
      对于UDP协议:若未指定绑定操作,那么可以通过下面connect操作来由内核负责套接字的绑定操作,若
connect又未指定,那么绑定操作只好通过套接字的写操作(sendto、sendmsg)来指定目的地址、端口,这时
套接字本地地址不会指定,为通配地址,而本地端口由内核指定,第一次sendto操作之后,插口的本地端口经
过内核指定之后就不会更改。
     对于TCP协议:若未指定绑定操作,可以通过下面connect操作来由内核负责套接字的绑定操作。内核会根
据套接字中的目的地址来判断外出接口,然后指定该外出接口的IP地址为插口的本地地址。Connect操作对于TCP
协议的客户端是必不可少的,必须指定。

 

(4)非阻塞的 socket connect
非阻塞模式有3种用途
        1.三次握手同时做其他的处理。connect要花一个往返时间完成,从几毫秒的局域网到几百毫秒或几秒的广域网。这段时间可能有一些其他的处理要执行,比如数据准备,预处理等。
        2.用这种技术建立多个连接。这在web浏览器中很普遍.
        3.由于程序用select等待连接完成,可以设置一个select等待时间限制,从而缩短connect超时时间。多数实现中,connect的超时时间在75秒到几分钟之间。有时程序希望在等待一定时间内结束,使用非阻塞connect可以防止阻塞75秒,在多线程网络编程中,尤其必要。   例如有一个通过建立线程与其他主机进行socket通信的应用程序,如果建立的线程使用阻塞connect与远程通信,当有几百个线程并发的时候,由于网络延迟而全部阻塞,阻塞的线程不会释放系统的资源,同一时刻阻塞线程超过一定数量时候,系统就不再允许建立新的线程(每个进程由于进程空间的原因能产生的线程有限),如果使用非阻塞的connect,连接失败使用select等待很短时间,如果还没有连接后,线程立刻结束释放资源,防止大量线程阻塞而使程序崩溃。

/*----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

   在一个 CLIENT/SERVER模型的网络应用中,客户端的调用序列大致如下:

        socket -> connect -> recv/send -> close

        其中socket没有什么可疑问的,主要是创建一个套接字用于与服务端交换数据,并且通常它会迅速返回,此时并没有数据通过网卡发送出去,而紧随其后的connect函数则会产生网络数据的发送,TCP的三次握手也正是在此时开始,connect会先发送一个SYN包给服务端,并从最初始的CLOSED状态进入到SYN_SENT状态,在此状态等待服务端的确认包,通常情况下这个确认包会很快到达,以致于我们根本无法使用netstat命令看到SYN_SENT状态的存在,不过我们可以做一个极端情况的模拟,让客户端去连接一个随意指定服务器(如IP地址为88.88.88.88),因为该服务器很明显不会反馈给我们SYN包的确认包(SYN ACK),客户端就会在一定时间内处于SYN_SENT状态,并在预定的超时时间(比如3分钟)之后从connect函数返回,connect调用一旦失败(没能到达ESTABLISHED状态)这个套接字便不可用,若要再次调用connect函数则必须要重新使用socket函数创建新的套接字。


下面结合实例分析,客户端代码如下:

[cpp] view plaincopy
  1. /** 
  2.  * client.c 
  3.  * 
  4.  * TCP client program, it is a simple example only. 
  5.  * Writen By: Zhou Jianchun 
  6.  * Date: 2011.08.11 
  7.  * 
  8.  * Compiled With: gcc -o client client.c 
  9.  * Tested On: Ubuntu 11.04 LTS 
  10.  * gcc version: 4.5.2 
  11.  * 
  12.  */  
  13.   
  14. #include <stdio.h>  
  15. #include <sys/socket.h>  
  16. #include <unistd.h>  
  17. #include <sys/types.h>  
  18. #include <netinet/in.h>  
  19. #include <stdlib.h>  
  20. #include <string.h>  
  21. #include <errno.h>  
  22.   
  23. #define SERVER_PORT 20000  
  24.   
  25. void usage(char *name)  
  26. {  
  27.     printf("usage: %s IP\n", name);  
  28. }  
  29. int main(int argc, char **argv)  
  30. {  
  31.     int server_fd, client_fd, length = 0;  
  32.     struct sockaddr_in server_addr, client_addr;  
  33.     socklen_t socklen = sizeof(server_addr);  
  34.   
  35.     if(argc < 2)  
  36.     {  
  37.         usage(argv[0]);  
  38.         exit(1);  
  39.     }  
  40.     if((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  
  41.     {  
  42.         printf("create socket error, exit!\n");  
  43.         exit(1);  
  44.     }  
  45.     srand(time(NULL));  
  46.     bzero(&client_addr, sizeof(client_addr));  
  47.     client_addr.sin_family = AF_INET;  
  48.     client_addr.sin_addr.s_addr = htons(INADDR_ANY);  
  49.   
  50.     bzero(&server_addr, sizeof(server_addr));  
  51.     server_addr.sin_family = AF_INET;  
  52.     inet_aton(argv[1], &server_addr.sin_addr);  
  53.     server_addr.sin_port = htons(SERVER_PORT);  
  54.   
  55.     if(connect(client_fd, (struct sockaddr*)&server_addr, socklen) < 0)  
  56.     {  
  57.         printf("can not connect to %s, exit!\n", argv[1]);  
  58.         printf("%s\n", strerror(errno));  
  59.         exit(1);  
  60.     }  
  61.     return 0;  
  62. }  

编译完成之后执行:

[cpp] view plaincopy
  1. zhou@neptune:~/data/source$ ./client 88.88.88.88  

此时程序会在connect函数中阻塞等待,约180秒之后输出:

[cpp] view plaincopy
  1. can not connect to 88.88.88.88, exit!  
  2. Connection timed out  

此刻connect的返回值为ETIMEOUT。

在此过程中我们可以用netstat命令查询连接状态:

[cpp] view plaincopy
  1. zhou@neptune:~/data/source$ sudo netstat -natp |grep 20000  
  2. tcp        0      1 192.168.0.4:44203       88.88.88.88:20000       SYN_SENT    5954/client      

可以看到此时的TCP连接状态为SYN_SENT,也就意味着发送了SYN包之后一直未得到服务端回馈SYN ACK包。

接下来我们使用这个客户端程序来连接自己的机器,测试时我的IP地址是192.168.0.4,是一个无线局域网,结果如下:

[cpp] view plaincopy
  1. zhou@neptune:~/data/source$ ./client 192.168.0.4  
  2. can not connect to 192.168.0.4, exit!  
  3. Connection refused  

因为我的机器上并没有跑在指定端口(20000)上监听的服务端程序,所以这个连接直接被协议栈拒绝(通过发送RST类型的TCP包),connect立刻返回,返回值为ECONNREFUSED。

再来看看去连接同一局域网中一台不存在的主机时的情形,比如这台想象的主机的IP地址为192.168.0.188:

[cpp] view plaincopy
  1. zhou@neptune:~/data/source$ ./client 192.168.0.188  
  2. can not connect to 192.168.0.188, exit!  
  3. No route to host  

因为本地局域网中的该主机并不存在,ARP请求得不到回应,网关会回应主机不可达的ICMP报文,connect返回EHOSTUNREACH。

至此connect函数的分析就结束了,由于本人水平有限,博客中的不妥或错误之处在所难免,殷切希望读者批评指正。同时也欢迎读者共同探讨相关的内容,如果乐意交流的话请留下您宝贵的意见,谢谢。



0 0