Socket编程

套接字

套接字是Socket编程比不可少的一环,因此,可以先给套接字下个定义:为区分不同的应用进程和应用进程间的连接(计算机间通信实体),操作系统就为应用程序进程和TCP/IP协议间交互提供了称为套接字的接口.这其中涉及到一种常用的思想,就是当两个实体间交互难度大时(或者说联系比较抽象时),往往都是使用中间实体,亦是中间层,后面有具体例子.

从套接字定义中,不难发现套接字涉及到了计算机网络五层结构中的应用层及运输层(这方面不熟悉的自行脑补).所以作为开场前的口水话,必须先明确一般意义上的计算机之间的通信实体其实指的是应用进程间的通信.

计算机间的通信实体:

       一台计算机可以同时运行多个程序,运行着的程序也就是所谓的进程.联系上网习惯,开着浏览器,听着音乐,聊着QQ,这里面的每个进程都位于应用层,故而也称为应用进程,也是一般情况下我们直接操作交互的.因此,一般意义上说的不同计算机间的通信,其实更具体的说应该是不同计算机中的一对”对等实体”间的通信,所谓的”对等实体”,是指位于不同计算机中相互通信的应用进程,也可以形象的理解为服务端程序和客户端程序.

       一台计算机的应用进程可以有很多个,更多的是取决于机子的性能,每个应用进程都是独立的、互不影响的与其他计算机中相应的应用进程进行通信.例如,可以打开十几个浏览器窗口,使用QQ软件聊天,使用BT下载电影,听音乐等,此时,每个浏览器窗口可以和对应的网站服务器应用进程通信,而QQ软件和腾讯服务器应用进程通信,都能够和正常的运行.

         计算机间的通信可以形象的使用如下表示:

        

这时就有两对”对等实体”参与通信,可以记为:

对等实体1:(计算机A:进程1, 计算机B:进程1)

对等实体2:(计算机B:进程2, 计算机C:进程1)

如果你比较敏感的话,就应该会发现为什么计算机A的进程1不会将消息发送到计算机C (因为IP)或者为什么不会发送到计算机B中的除进程1以外的其他进程(因为端口,后面介绍).

原因如下:

运输层的复用和分用:

一般情况下,计算机只有一块网卡,网络层实体和运输层实体也通常只有一个,计算机中的所有应用进程都是使用同一块网卡、同一网络实体与同一运输层实体来收发数据.那么为什么浏览器、QQ、BT等应用进程都能正常工作,没有出现数据混乱现象,收到的数据总能被正确的分配到不同的应用进程中去.没错,这就是运输层的复用和分用功能.复用指发送计算机的运输层收集多个应用进程的数据,使用同一网络层实体提供的服务把数据发送出去;分用指接收计算机的运输层收到来自网络层实体交来的数据后,正确分配到相应的应用进程中去.

复用和分用(图中箭头全部往上)如图所示:

自然而然的,运输层为了实现复用和分用功能,就必须能够区分不同的应用进程,以便能够正确的分配数据.所以,运输层区分不同应用进程的方法就是给每个应用进程设置不同的标识,这个标识就是端口号.一个端口号由16位二进制组成,范围是0~2^16-1,即0~65535.

众所皆知,IP地址唯一的标识了世界范围内的一台计算机,而端口则唯一的标识了一台计算机上的不同应用进程,那么,如果把IP地址和端口号组合起来,会出现什么情况?没错,这样就能够唯一的标识世界范围内的任何一个应用进程,将IP地址和端口号组合在一起,就叫做套接字(socket).套接字标识了世界范围内的一个应用进程.可以使用如下方法表示, 210.44.176.198:80,而对等实体间的通信也就可以使用一对套接字表示,这也可以说表示一个连接.例如计算机222.206.70.80中2000端口应用进程和计算机210.44.176.198中80端口应用进程间的通信

就记为:

(222.206.70.80:2000, 210.44.176.198:80)

毫无疑问,为了正确区分不同的应用进程,一台计算机中的端口号不能相同,但是不同计算机中的端口号可以相同,此时因为IP地址不同,并不会造成混乱.最终,运输层的协议数据单元”报文段”中有源端口号和目的端口号.源端口号是发送进程的端口号,目的端口是接收进程的端口号.而网络层的协议数据单元”数据包”中含有目的IP地址和源IP地址.这样便保证了一个数据将被发送到由目的IP地址指定的计算机中,并根据目的端口找到该计算机上相应的应用进程.从而,数据总能被正确的接受和发送到世界范围内的任何一个应用进程.


Socket编程

扯了一些基本知识后,进入正题,但只涉及和套接字相关的知识.

我们说了IP地址和端口号唯一的标识了世界范围内的一个应用进程,但我们并不能够直接操作运输层和网络层来指定端口号和IP地址的.既然不能操作这两个层,应用进程又如何能够与之进行交互呢?这个时候,操作系统就发挥了作用,操作系统就为应用程序进程和TCP/IP(运输层和网络层)协议间交互提供了的接口,而我们使用的是这个接口,当我们错误使用时,操作系统就能够判断,并不会造成错误.这个接口也就是套接字.可以形象如下表示:

这样,应用进程都是直接和Socket进行交互,而不用去担心运输层和网络层是怎么实现的,程序员在开发的过程中只需要理解这个Socket层能够提供什么服务,并正确调用这些服务,而剩余的事,就是由Socket和运输层之间去交互,我们并不需要去关心它们是怎么交互的,因为我们知道了Socket层给我们提供了什么服务,我们只要使用了这个服务,Socket层就会帮我们实现该有的功能.这大大加快了程序的开发效率和程序员的负担.这也就是一开始说的通过增加中间层来实现两个交互难度大的实体间的简单交互.

现在,看一下具体的编程例子.在服务器端,会有这么一句:

SOCKET s = ::socket(AF_INET,SOCK_STREAM, IPPROTO_TCP)

但是这里只是创建一个套接字的描述符,记住,只是一个描述符而已,因为不想再程序中总是写这样的形式: 210.44.176.198:8888,这样非常不直观.当然,这也导致了我们必须在之后进行地址的绑定,将这个套接字绑定到一个IP地址和某个端口.即:

   sockaddr_in   sin;

   sin.sin_family = AF_INET;

   sin.sin_port = htons(8888);

   sin.sin_addr.S_un.S_addr = INADDR_ANY;

   ::bind(s, (LPSOCKADDR)&sin, sizeof(sin)  //绑定

上面sin是一个地址结构,这个地址结构中AF_INET决定了要使用IPV4(32位)和端口号(16位)的组合.就是声明要使用IP和端口号组合的套接字的地址类型,8888就是我们这个程序要使用的端口号(端口号一般是运输层分配,但程序也可以在开发时指定,服务端肯定要指定这个端口号的,因为客户端的连接需要使用到这个端口号,而客户端则可以由运输层来分配,因此,客户端程序可以不进行地址绑定,而让客户端运输层自行根据当时运行的情况,选择一个合适的端口进行绑定.),这个端口号要和客户端中连接远程地址信息时一样.保证客户端程序能够将信息发送到正确的端口,也才能实现上面所说的不出现数据混乱,实现进程间的通信. INADDR_ANY指定使用本机的哪个IP地址,通常一台计算机只会有一个IP地址.最后,使用bind将套接字s绑定到这个地址结构,也就形成了这种:210.44.176.198:8888的形象的套接字结构.而客户端就是连接到这个套接字,即连接到IP地址是210.44.176.198的计算机上8888端口的应用进程.

相比之下,客户端就是需要连接了.客户端也是先声明一个套接字描述符: SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).

   然后设置要连接的服务器地址结构:

    // 填写远程地址信息

   sockaddr_in  servAddr;

   servAddr.sin_family = AF_INET;

   servAddr.sin_port = htons(8888);

   servAddr.sin_addr.S_un.S_addr= inet_addr("210.44.176.198");//连接

        ::connect(s, (sockaddr*)&servAddr, sizeof(servAddr))

   通过connect,说明客户端要连接到IP地址为210.44.176.198的服务器,在8888端口监听的应用进程.所以服务器设置监听端口和客户端指定要连接端口要一致.这里面还有一个问题,为什么客户端不需要绑定地址结构?其实这边是有绑定的,当我们没有显示指定绑定时,计算机会选择一个没有使用的端口号来标识这个应用进程和IP地址来绑定这个套接字描述符,这样,由于端口号是临时分配,也应该要临时分配,虽然端口号可以65535个,但是应用程序的总数量远比这个大得多,如果全部写死了,那最终会导致应用程序冲突,但是我们并不会同时运行大于65535个应用程序,所以根据实际情况临时分配会更符合实际.这样客户端进程就可以根据当时的运行情况,选择合适的端口号来通信.而我们其实更不必要知道被分配到什么样的端口号,因为在打开客户端程序时,我们就已经根据远程地址信息,连接到了远程服务器相应端口的进程,至于是这样的通讯实体: (210.44.176.198:8888,168.192.23.34:1500), 还是这样的通信实体:(210.44.176.198:8888,168.192.23.34:4500)其实是没有任何区别的,因为都能正常进行通信.有些计算机能帮我们完成的事,并不需要程序员来做.

       这样,服务端设置了自己的IP地址和端口号组合的套接字用来监听,而客户端设置自己的套接字连接到服务器上相应端口的应用进程,这样就保证了计算机间应用进程的通信.而之所以能通信,都是我们在开发过程中就已经在服务端程序中设置了相应的IP和端口,并且使客户端连接到相应服务器的对应进程,只要服务器和客户端都运行起来,自然就都能借用运输层和网络层来进行通信.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值