Chinaunix首页 | 论坛 | 博客
  • 博客访问: 12775
  • 博文数量: 12
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 110
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-01 16:24
个人简介

简简单单学习,快快乐乐生活。。。。

文章存档

2017年(1)

2016年(4)

2015年(8)

分类: 网络与安全

2015-10-04 16:49:24

一个Server-Client模型程序的开发原理:

  服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

  客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

  Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。 Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

  常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。 Socket建立为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:int socket(int domain, int type, int protocol);domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。 Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。 Socket执行体为你管理描述符表。两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。 socket在测量软件中的使用也很广泛

socket深层次理解

Socket编程基本就是listen,accept以及send,write等几个基本的操作。 

对于网络编程,我们也言必称TCP/IP,似乎其它网络协议已经不存在了。对于TCP/IP,我们还知道TCP和UDP,前者可以保证数据的正确和可靠性,后者则允许数据丢失。最后,我们还知道,在建立连接前,必须知道对方的IP地址和端口号。除此,普通的程序员就不会知道太多了,很多时候这些知识已经够用了。最多,写服务程序的时候,会使用多线程来处理并发访问。 

我们还知道如下几个事实:

1。一个指定的端口号不能被多个程序共用。比如,如果IIS占用了80端口,那么Apache就不能也用80端口了。

2。很多防火墙只允许特定目标端口的数据包通过。

3。服务程序在listen某个端口并accept某个连接请求后,会生成一个新的socket来对该请求进行处理。

于是,一个困惑了我很久的问题就产生了。如果一个socket创建后并与80端口绑定后,是否就意味着该socket占用了80端口呢?如果是这样的,那么当其accept一个请求后,生成的新的socket到底使用的是什么端口呢(我一直以为系统会默认给其分配一个空闲的端口号)?如果是一个空闲的端口,那一定不是80端口了,于是以后的TCP数据包的目标端口就不是80了--防火墙一定会组织其通过的!实际上,我们可以看到,防火墙并没有阻止这样的连接,而且这是最常见的连接请求和处理方式。我的不解就是,为什么防火墙没有阻止这样的连接?它是如何判定那条连接是因为connet80端口而生成的?是不是TCP数据包里有什么特别的标志?或者防火墙记住了什么东西?

后来,我又仔细研读了TCP/IP的协议栈的原理,对很多概念有了更深刻的认识。比如,在TCP和UDP同属于传输层,共同架设在IP层(网络层)之上。而IP层主要负责的是在节点之间(End to End)的数据包传送,这里的节点是一台网络设备,比如计算机。因为IP层只负责把数据送到节点,而不能区分上面的不同应用,所以TCP和UDP协议在其基础上加入了端口的信息,端口于是标识的是一个节点上的一个应用。除了增加端口信息,UPD协议基本就没有对IP层的数据进行任何的处理了。而TCP协议还加入了更加复杂的传输控制,比如滑动的数据发送窗口(Slice Window),以及接收确认和重发机制,以达到数据的可靠传送。不管应用层看到的是怎样一个稳定的TCP数据流,下面传送的都是一个个的IP数据包,需要由TCP协议来进行数据重组。

所以,我有理由怀疑,防火墙并没有足够的信息判断TCP数据包的更多信息,除了IP地址和端口号。而且,我们也看到,所谓的端口,是为了区分不同的应用的,以在不同的IP包来到的时候能够正确转发。

TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。就像操作系统会提供标准的编程接口,比如Win32编程接口一样,TCP/IP也必须对外提供编程接口,这就是Socket编程接口--原来是这么回事啊!

在Socket编程接口里,设计者提出了一个很重要的概念,那就是socket。这个socket跟文件句柄很相似,实际上在BSD系统里就是跟文件句柄一样存放在一样的进程句柄表里。这个socket其实是一个序号,表示其在句柄表中的位置。这一点,我们已经见过很多了,比如文件句柄,窗口句柄等等。这些句柄,其实是代表了系统中的某些特定的对象,用于在各种函数中作为参数传入,以对特定的对象进行操作--这其实是C语言的问题,在C++语言里,这个句柄其实就是this指针,实际就是对象指针啦。

现在我们知道,socket跟TCP/IP并没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以,socket的出现只是可以更方便的使用TCP/IP协议栈而已,其对TCP/IP进行了抽象,形成了几个最基本的函数接口。比如create,listen,accept,connect,read和write等等。

现在我们明白,如果一个程序创建了一个socket,并让其监听80端口,其实是向TCP/IP协议栈声明了其对80端口的占有。以后,所有目标是80端口的TCP数据包都会转发给该程序(这里的程序,因为使用的是Socket编程接口,所以首先由Socket层来处理)。所谓accept函数,其实抽象的是TCP的连接建立过程。accept函数返回的新socket其实指代的是本次创建的连接,而一个连接是包括两部分信息的,一个是源IP和源端口,另一个是宿IP和宿端口。所以,accept可以产生多个不同的socket,而这些socket里包含的宿IP和宿端口是不变的,变化的只是源IP和源端口。这样的话,这些socket宿端口就可以都是80,而Socket层还是能根据源/宿对来准确地分辨出IP包和socket的归属关系,从而完成对TCP/IP协议的操作封装!而同时,放火墙的对IP包的处理规则也是清晰明了,不存在前面设想的种种复杂的情形。(以上为转载)

例子:/*
服务器端程序:
1、创建socket
2、将套接字绑定到一个本地地址和端口上bind
3、将套接字设为监听模式,准备接受客户请求listen
4、等待客户请求到来;当请求到来后,结合搜此次连接的套接字accept
5、用返回的套接字和客户端进行通信send/recv
6、返回,等待另一客户请求。
7、关闭套接字。
*/

#include "WinSock2.h"
#include "stdio.h"
#pragma comment(lib,"Ws2_32.lib")//导入库,lib表示库," "表示库的名称 


int main(int argc, char* argv[]) 
{
//加载WinSock库 
WORD wVersionRequested;
WSADATA wsaData;
int err; 


wVersionRequested = MAKEWORD( 2, 2 );


err = WSAStartup( wVersionRequested, &wsaData ); 
if ( err != 0 ) return 0; 


if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )

WSACleanup( ); 
return 0; 



SOCKET lSocket,aSocket; //SOCKET句柄
SOCKADDR_IN AddrSer; 
AddrSer.sin_family=AF_INET; //网络类型/指代协议族
AddrSer.sin_port=htons(5000); //端口号,使用网络字节顺序,短整形
//存储IP地址,使用in_addr这个数据结构
AddrSer.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//INADDR_ANY表示接受任何机器的请求 ,长整形
lSocket=socket(AF_INET,SOCK_STREAM,0); 
//绑定服务器地址 
if(::bind(lSocket,(SOCKADDR*)&AddrSer,sizeof(SOCKADDR))==SOCKET_ERROR)

printf("绑定套接字失败"); 
return 0; 

::listen(lSocket,1);//开始监听客户端的连接请求 
SOCKADDR_IN AddrCli;//客户端的地址信息 
int len=sizeof(SOCKADDR); 
aSocket=::accept(lSocket,(SOCKADDR*)&AddrCli,&len);//连接成功时,产生新的socket用来与客户端通信 
char recBuf[1024]; 
char senBuf[1024]; 
while (1) 

//TCP下为recv与send,UDP下为recefrom与sendto 
::recv(aSocket,recBuf,1024,0); 
if (recBuf[0]=='#') 

printf("客户端请求断开\n"); 
printf("正在断开..."); 
Sleep(3000); 
break; 

printf("客户端:%s\n",recBuf); 
printf("服务器:"); 
gets(senBuf);//warming:gets()is unsafe 
if (senBuf[0]=='#') 

::send(aSocket,senBuf,1024,0); 
printf("正在请求断开...\n"); 
Sleep(3000); 
return 0; 

//printf("服务器:%s\n",senBuf); 
::send(aSocket,senBuf,1024,0); 
memset(recBuf,' ',1024); 
memset(senBuf,' ',1024); 

::closesocket(aSocket); 
::closesocket(lSocket); 
::WSACleanup(); 
return 0; 
}

/*
客户端程序:
1、创建套接字socket
2、想服务器发出连接请求connect
3、和服务器进行通信send/recv
4、关闭套接字。
*/


//#include "stdafx.h"
#include "WinSock2.h" 
#include "stdio.h" 
#pragma comment(lib,"Ws2_32.lib") 


int main(int argc, char* argv[]) 

WORD wVersionRequested; 
WSADATA wsaData; 
int err; 


wVersionRequested = MAKEWORD( 2, 2 ); 


err = WSAStartup( wVersionRequested, &wsaData ); 
if ( err != 0 ) return 0; 
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )

WSACleanup( ); 
return 0; 



SOCKET cSocket; 
cSocket=socket(AF_INET,SOCK_STREAM,0); 
SOCKADDR_IN addrSer; 
addrSer.sin_family=AF_INET; 
addrSer.sin_port=htons(5000); 
addrSer.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); 
if(::connect(cSocket,(SOCKADDR*)&addrSer,sizeof(SOCKADDR))==SOCKET_ERROR) 

printf("连接服务器失败"); 
return 0; 

char senBuf[1024]; 
char recBuf[1024]; 
while(1) 

printf("客户端:"); 
gets(senBuf); 
if (senBuf[0]=='#') 

::send(cSocket,senBuf,1024,0); 
printf("正在请求断开...\n"); 
Sleep(3000); 
return 0; 

//printf("客户端:%s\n",senBuf); 
::send(cSocket,senBuf,1024,0); 
::recv(cSocket,recBuf,1024,0); 
if(recBuf[0]=='#') 

printf("与服务器断开"); 
printf("正在断开..."); 
Sleep(3000); 
break; 

printf("服务器:%s\n",recBuf); 
memset(recBuf,' ',1024); 
memset(senBuf,' ',1024); 

::closesocket(cSocket); 
::WSACleanup(); 
return 0; 



阅读(324) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~