精华内容
下载资源
问答
  • 1.聊天窗口时有非当前用户消息传递过来时,采用了notification机制,不过貌似有BUG。但代码里还有被注释掉startActivityforResult方法 + toast方法,用这个可以完美解决非当前用户传递消息如何处理消息过程。 ...
  • 基于TCP/IP的局域网多用户通信

    千次阅读 2013-02-19 15:47:54
    引言 由于因特网迅速流行,越来越多应用程序...一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点连接,而本文在VC6.0环境下,运用了了多线程以及共享数据结构技术,不仅实现了多用户与服务器间
    引言
    

    由于因特网的迅速流行,越来越多的应用程序具备了在网上与其它程序通信的能力。从WIN95开始微软把网络功能融进了它的操作系统,使得应用程序网络通信能力更为普及。因此,微软的TCP/IP协议也就成为网络应用程序基于的首选协议。

    一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点的连接,而本文在VC6.0的环境下,运用了了多线程以及共享数据结构技术,不仅实现了多用户与服务器间的连接,而且解决了多用户间信息互发问题----依靠服务器的转发功能。通过本文的阐述,希望能对那些需要编写多用户网络通信程序的读者以启发。

    一、技术概述

    1.1 基于TCP/IP的通信技术

    基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。下面简要地讲一下设计思路(VC6.0下):

    第一部分 服务器端

    一、创建服务器套接字(create)。

    二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。

    三、接受来自用户端的连接请求(accept)。

    四、开始数据传输(send/receive)。

    五、关闭套接字(closesocket)。

    第二部分 用户端

    一、创建用户套接字(create)。

    二、与远程服务器进行连接(connect),如被接受则创建接收进程。

    三、开始数据传输(send/receive)。

    四、关闭套接字(closesocket)。

    通过以上设计思路,我们可以创建一个简单的面向连接的单用户程序。下面,将介绍多线程技术,以使程序支持多用户。

    1.2 多线程技术

    我们可以把线程看成是一个进程(执行程序)中的一个执行点,每个进程在任何给定时刻可能有若干个线程在运行。一个进程中的所有线程共享该进程中同样的地址空间,同样的数据和代码,以及同样的资源。进程中每个线程都有自己独立的栈空间,和其它线程分离,并且不可互相访问。每个线程在本进程所占的CPU时间内,要么以时间片轮换方式,要么以优先级方式运行。如果以时间片轮换方式运行,则每个线程获得同样的时间量;如果以优先级方式运行,则优先级高的线程将得到较多的时间量,而优先级低的线程只能得到较少的时间量。方式的选择主要取决于系统时间调度器的机制以及程序的实时性要求。

    现在,运用多线程技术就可以实现对多用户的支持。即在服务器端,使接收来自用户端的连接请求(accept)这步无限循环,每接收一个用户请求,产生两个线程(send和receive线程),用来管理服务器与该用户的通信任务。下面,运用共享数据结构技术,就可以实现本问所要解决的关键技术---服务器转发技术。

    1.3 共享数据结构技术

    同一进程中的多个线程共存于单一的线性地址空间,因此,在多线程间共享数据结构是非常容易且方便的。但必须注意的是,对数据结构的访问必须是多线程互斥的,否则数据任意更改将导致不可预料的结果。本文所阐述的服务器转发技术也就是通过共享数据结构实现线程间的互相通信。

    二、实现方案

    整体方案的构思图如下:

    通过上图,我们可以看到整个系统分为三个相关的程序,即注册/登陆服务器、通信服务器以及用户程序。其中,注册/登陆服务器负责用户的注册、登陆以及数据库管理;通信服务器负责完成数据转发以及共享数据结构的管理;用户端则完成注册、登陆和通信功能。为什么要把服务器分为两部分呢?主要是考虑到服务器的用户容量问题,以及对通信服务器的保护,只有在通过验证后,用户在能与通信服务器连接。

    由此可见,整个系统通信任务的实现还是很复杂的。用户端首先必须注册自己,等待注册成功;然后根据自己的注册信息进行服务器登陆,登陆成功后才能与通信服务器连接,进行用户间通信。

    注册/登陆服务器接收到用户端的信息后,首先判断是注册信息还是登陆信息。如果是注册信息,则将该数据按预定的格式写入数据库,然后返回注册成功的消息,期间有任何异常产生,服务器都会返回注册失败消息,提示用户重新注册;如果是登陆信息,则从数据中提取用户名和ID与数据库中的内容进行比较,如果该用户存在,则返回登陆成功消息,反之,返回登陆失败消息。

    通信服务器所完成的主要功能是数据转发,这是通过与图中的共享数据结构进行交互完成的。服务器接收到用户端发来的消息后,提取消息的一部分与共享数据结构存储的内容进行比较,确定所要转发的对象,最后通过多线程及其通信机制完成数据转发。 下面,我们将分三部分来讨论系统的具体实现过程。

    三、具体实施

    3.1 注册/登陆服务器

    注册/登陆服务器程序是基于对话框的,该程序使用I/O端口56789与用户端连接。

    首先,在对话框初始化的同时完成网络初始化,即执行Init_net()函数,代码(不完整)如下:

    01.BOOL CServerDlg::Init_net()
    02.{网络初始化///
    03. addrLen=sizeof(SOCKADDR_IN);
    04. status=WSAStartup(MAKEWORD(1, 1), &Data);
    05. ………
    06. memset(&serverSockAddr, 0, sizeof(serverSockAddr));
    07.
    08./*以下指定一个与某个SOCKET连接本地或远程地址*/
    09.
    10. serverSockAddr.sin_port=htons(PORT);
    11. serverSockAddr.sin_family=AF_INET;
    12. serverSockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    13. serverSocket=socket(AF_INET, SOCK_STREAM, 0);//初始化SOCKET
    14. ………
    15.
    16. status=bind(serverSocket,(LPSOCKADDR)&serverSockAddr,sizeof(serverSockAddr)); //将SOCKET与地址绑定
    17. ………
    18. status=listen(serverSocket, 5); //开始监听
    19. ………
    20. return true;
    21.}

    接着按下RUN键开始服务器功能,执行Reg_Load()函数,使服务器始终处于等待连接状态,但这样也使该线程始终阻塞。当有用户连接时,该函数创建一个任务用于处理与用户及数据库的事务。具体任务函数略(详见原始代码文件)。

    01.void CServerDlg::Reg_Load()
    02.{
    03. while(1)
    04. {
    05. CWinThread* hHandle;
    06. clientSocket=accept(serverSocket,(LPSOCKADDR)&clientSockAddr,&addrLen); //等待连接,阻塞
    07. hHandle=AfxBeginThread(talkToClient,(LPVOID)clientSocket);//有连接时,创建任务
    08. ………
    09. }
    10.}

    任务函数在接收到消息时,要对数据库进行操作,由于数据库较简单,采用ODBC连接ACCESS数据库(将netuser.mdb在ODBC数据管理器中安装成同名数据源)具体代码略。

    3.2 通信服务器

    通信服务器是本程序实现的关键,它运用共享数据结构技术及多线程技术,通过I/O端口56790与用户端连接,实现了数据转发功能。首先,程序初始化网络Init_net(),接着当用户连接到服务器时,创建接收线程和发送线程,这样就可以实现数据转发。最后,当用户断开连接时,服务器关闭与他的连接,并结束相应的线程。

    下面我们来看一下本程序中的共享数据结构的具体内容与使用方法以及多线程的相关内容与实现。

    ● 共享数据结构

    本程序的共享数据结构一共有两个,即socket_info和send_info。前者包含了所有登陆用户的一些基本资料,后者则包含了当前服务器接收到的用户端所发送的信息资料。详细内容及注释如下:

    01.struct socket_info
    02.{
    03. SOCKET s_client; //用户的SOCKET值
    04. u_long client_addr; //用户网络地址
    05. CString pet; //用户昵称
    06. CWinThread* thread; //为该用户创建的发送线程对象的指针
    07.};
    08.
    09.struct send_info
    10.{
    11. CString data; //用户端发送的数据内容(经过编辑)
    12. CWinThread* thread; //需要发送数据的任务指针
    13.};

    在程序中,定义两个全局变量,用来在线程间共享:

    1.send_info info_data; CLists_info;

    每当有用户连接到服务器,服务器就将用户端的一些信息以socket_info结构体的形式存入s_info列表中;而当服务器接收到用户端发送过来的数据时,就将数据格式化后存入结构体info_data,通过与结构体列表比较,确定需要恢复的发送线程(所有发送线程在创建时都被挂起)。这样,服务器就准确地转了发数据。

    ●多线程

    每当服务器上有用户连接成功,服务器都会为其创建两个线程:接收线程(RecvData)和发送线程(SendData),并且接收线程在创建后处于可执行状态,而发送线程则阻塞,等待服务器将其唤醒。这两个线程都执行一个无限循环的过程,只有当通信出现异常或用户端关闭连接时,线程才被自身所结束,并且,这两个线程一定是同时生成,同时结束的。很显然,每个连接产生两个线程,使得数据转发变的简单,但同时又使得服务器的任务加重。因此,用户端的连接数量有所限制,视服务器软、硬件能力而定。

    同时,由于多线程对结构体info_data都需要操作,所以线程间必须同步。这儿,我定义了互斥量CMutex m_mutex,用它的方法Lock()和Unlock()来完成同步。

    我们首先来看一下接收线程(RecvData):(不完整代码)

    01.UINT RecvData(void* cs)
    02.{
    03. SOCKET clientSocket=(SOCKET)cs;
    04. while(1)
    05. {
    06. numrcv=recv(clientSocket, buffer, MAXBUFLEN, NO_FLAGS_SET);
    07. buffer[numrcv]=''\0'';
    08. if(strcmp(buffer,"Close!")!=0) //不是接收的“Close”数据
    09. {
    10. …………
    11. for(i=0;iResumeThread(); //恢复发送相应的线程
    12. break;
    13. }
    14. }
    15. }
    16. else
    17. {
    18. …………
    19. if(clientSocket==s1.s_client)
    20. {
    21. m_mutex.Lock(); //互锁
    22. info_data.data=buffer;
    23. m_mutex.Unlock(); //解锁
    24. s1.thread->ResumeThread(); //恢复发送相应的线程
    25. s_info.RemoveAt(po1); //删除该用户信息
    26. break;
    27. }
    28. ………
    29. goto aa;
    30. }
    31. }
    32. aa: closesocketlink((LPVOID)clientSocket); //关闭连接
    33. AfxEndThread(0,true); //结束本线程
    34. return 1;
    35.}

    接下来看一下发送线程(SendData):(不完整代码)

    01.UINT SendData(void* cs)
    02.{
    03. SOCKET clientSocket=(SOCKET)cs;
    04. while(1)
    05. {
    06. if(info_data.data!="Close!")
    07. {
    08. m_mutex.Lock(); //互锁
    09. numsnd=send(clientSocket,info_data.data,
    10. info_data.data.GetLength(),NO_FLAGS_SET); //发送数据
    11. now=info_data.thread;
    12. m_mutex.Unlock(); //解锁
    13. now->SuspendThread(); //自身挂起
    14. }
    15. else
    16. { goto bb; }
    17.
    18. }
    19. bb: closesocketlink((LPVOID)clientSocket); //关闭连接
    20. AfxEndThread(0,true); //结束本线程
    21. return 1;
    22.}

    3.3 用户端

    很显然,用户端不用考虑多线程,网络连接技术也比较成熟,因此在通信方面没有什么难题。但是,用户端是面向实际用户的,所以,不论是界面还是功能都必须友好。就像大多数软件的更新一样,界面友好度的提高以及功能的完善往往是放在首位的。由此可见,单从总体设计与技术实现角度来讲,用户端的工作量是十分大的,并且设计较服务器端复杂得 多。我粗略总结了以下几条:

    ●与服务器通信格式兼容;

    ●操作简单、易用,有美观的界面及快捷键;

    ●准确地接收和传输数据;

    ●所有的数据记录与提取功能;

    ●多种消息接收提示方式,比如托盘图标(发送者头像)闪烁、声音提示等;

    根据以上内容,我设计了三个独立的对话框分别用来完成注册、登陆、通信功能,登陆和注册对话框与服务器的56789I/O端口连接,通信对话框与服务器的56790I/O端口连接,这样就很好地实现了注册登陆与通信的隔离,既能使服务器负载降低,同时又能保证一定的通信安全性。

    由于本部分不是主要内容,详细代码见程序。

    四、结束语

    通过以上阐述可以知道,本系统分为服务器端和用户端,服务器端又分为注册/登陆服务器和通信服务器,通过通信服务器的转发功能实现了局域网内的多用户通信功能。本文运用了多线程技术和共享数据结构技术实现了通信服务器的转发功能,使一般基于TCP/IP的网络应用程序得到了发展。本系统已经在我实验室的局域网(一台服务器,二十台客户机)运行通过。

    参考文献:

    [1] Eugene Olafsen ,Kenn Scribner, K.David White等著. MFC Visual C++ 6.0编程技术内幕. 北京:机械工业出版社 2000.2

    [2] Charles Wright. Visual C++程序员实用大全. 北京:中国水利水电出版社 2001.10

    作者信息: 袁 渊(华东船舶工业学院 机械系,江苏 镇江 212003)

    转自:http://www.vckbase.com/index.php/wv/200

     

     

     

     

     

     

     

     

     

     

     

    下面还有一篇文章:对上述文章的补充:

     

     

    《基于TCP的局域网多用户通信、文件传送程序详解》

     

    看了袁渊先生在VC知识库《在线杂志》第14期发表的文章《基于TCP/IP的局域网多用户通信》,感觉受益颇多,但也觉得里面有一些不太完善的地方,具体来说主要有:

    1.两个服务器单独运行,且主线程均阻塞,用户界面死锁,不便于控制;

    2.聊天服务器线程和互斥量的使用可能导致死锁;

    3.不能实现文件传送(文件传送可不能由服务器转发,否则非把它累趴下不可^-^);

    4.不能由用户进行网络设置,所以在不同的网络使用必须修改源程序等等;

    我在此基础上重新设计编写了一个系统,具体如下:

    一、构架设计

    整个系统分为三个相关的程序模块,即注册登陆服务器(wbQQRegSer)、聊天通信服务器(wbQQChat)以及用户程序(wbQQClient)。其中,注册登陆服务器负责用户的注册、登陆以及数据库管理;通信服务器负责完成数据转发以及共享数据结构的管理;用户端则完成注册、登陆、通信和文件传送功能。在进行文件传送时,任一客户程序均可以既作为文件传送服务器发送文件,也可以作为客户端接收文件,实现半双工的文件传送。整个系统构成如图一:

    图一 系统构架图

    二、注册登录服务器设计

    注册登录服务器采用面向连接的并发式方式,服务器设计成为一个对话框程序。调用WSAStartup初始化动态库,socket函数创建套接字,bind函数绑定本地IP地址和端口,listen函数使套接字进入侦听,然后由于调用accept()函数将产生阻塞,所以不宜在主线程中调用该函数,因而在初始化网络后当用户按下“运行注册登录服务器”按钮后,利用侦听套接字启动注册登录线程RegLoad(void *s)进入无限循环,在线程中调用accept函数,用来接受来自客户端的连接请求,每当一个连接请求到来时,accept()函数将产生一个新的套接字,利用这个套接字产生一个新的线程talkToClient(void *cs)与客户端进行通信并读写数据库,通信完毕后关闭该套接字和线程,原来的侦听套接字继续处于侦听状态。

    两个服务器程序可以在同一台物理机器上运行,也可以在不同的机子上运行,为方便服务器的控制,在注册登录服务器调用函数

    01.CreateProcess( NULL,
    02.        ".\\..\\wbQQChat\\wbQQChat.exe",    // Command line.
    03.        NULL,             // Process handle not inheritable.
    04.        NULL,             // Thread handle not inheritable.
    05.        FALSE,            // Set handle inheritance to FALSE.
    06.        0,                // No creation flags.
    07.        NULL,             // Use parent''''s environment block.
    08.        NULL,             // Use parent''''s starting directory.
    09.        &si,              // Pointer to STARTUPINFO structure.
    10.        &pi )
    11.  

    创建聊天通信服务器进程,想关闭时则调用TerminateProcess(m_hProcChat, 2)函数关闭此进程。

    三、聊天通信服务器设计

    聊天通信服务器设计为无界面的进程(创建时先建一个基于对话框的应用程序,然后把对话框类删除,把APP类里面与对话框有关的语句全删除即可创建无界面进程),采用共享数据结构,为每个客户端创建两个线程,实现接收和转发的功能。第一个线程用于发送,

    1.hHandleSend = AfxBeginThread(SendData,(LPVOID)clientSocket,0,0,CREATE_SUSPENDED,NULL);

    第二个线程用于接收:

    1.hHandleRecv = AfxBeginThread(RecvData, (LPVOID)clientSocket);

    四、客户端设计

    客户端设计成为对话框的用户界面,主要分成四个模块,分别是注册模块、登录模块、聊天模块和文件传送模块。

    在程序运行后的第一个对话框,客户可以选择登录或注册,若是注册则启动注册向导,分三步完成注册工作,第一步为基本信息登记,包括头像选择、用户名、性别、密码,其中用户名和密码将在注册成功后登录使用。第二步为详细资料,包括真实姓名、城市、E-mail地址和电话号码。第三步为网络设置,分别是注册登录服务器的IP地址和端口号,聊天通信服务器的IP地址和端口号,也就是说两个服务器程序可以分别位于不同的物理机器,以减轻服务器运行时的负荷。点击确定后,客户端将与指定的IP地址和端口号去连接注册登录服务器,成功连接后服务器执行注册操作,并返回注册结果。

    客户注册成功后,即可用注册时的用户名和密码进行登录,将登录信息按注册时的网络设置发往服务器,服务器执行登录操作并返回注册结果,登录成功则连接聊天通信服务器,否则退出程序。

    登录成功出现聊天对话框,可以从下拉组合框选择好友,发送信息的同时将信息写入聊天记录文件,服务器收到信息后依照接收者用户名进行转发。若客户收到信息则闪动托盘处的图标,提示用户收到信息,用户可以点击回答进行回复。

    当登录成功后,用户也可以在选择好友后点击传送文件按钮来进行文件传送。当客户A向客户B发送文件时,A弹出传送文件对话框,提示给B发送文件,等待B的回应,客户B将弹出消息框告知A向B发送文件,B可以接收也可以拒收。文件收发完毕后,点击关闭按钮关闭文件传送对话框。

    五、网络传输协议设计

    为了让客户端和服务器能够协同工作,必须在通信过程中定义一套规则也就是协议,让双方能够相互听懂,并依照协议执行相应的功能块。

    客户端注册时发送的消息为Reg: + BasicDlg.m_strUserName + BasicDlg.m_nAge + sex + BasicDlg.m_strPassWd + MiscDlg.m_strTruName + MiscDlg.m_strCity + MiscDlg.m_strEmail + res + MiscDlg.m_strTel,注册时发送消息的头部为Reg。登录时发送的消息为:Load: + m_strUserName + m_strPassWd,登录时发送消息的头部为Load。注册登录服务器收到客房端的消息后检查其头部,若是Reg则执行注册操作,注册成功则返回success!,用户名已经存在则返回exist!,其它原因注册不成功则返回Error!;若是Load则执行登录操作,登录成功返回success!,登录不成功则返回error!。客户端依照返回信息做出相应提示,并执行相应功能模块。

    登录成功后,客户端将自己的用户名发送给聊天通信服务器,服务器为客户端创建一个套接字,两个线程,并填充socketInfo结构,连入链表。客户端发送消息结构为:“接收者用户名” + “:” + “发送者头像ID” + “~” + “(星期、月、日、年、时、分、秒)” +"\t" +"发送者用户名" +“->” + “接收者用户名” +"\n\r" + “发送的消息”,其头部均为接收者用户名,服务器依照用户名查找链表,截掉头部后把原信息进行转发,若客户端关闭, 则发送消息为Close!,服务器从链表中删除相应项。

    客户端可能收到的消息有三种,第一种为普通消息,结构如前所述;第二种为SendFile!,表示对方想向己方传送文件;第三种为Refuse!,表示对方拒绝接收己方文件。客户端A想给客户端B传送文件,则发送消息为SendFile!,B收到SendFile!后弹出消息框,提示对方向己方传送文件,接收按“是”,执行文件接收功能;拒绝按“否”,发送Refuse!

    六、附加说明

    1.本软件在win2000professionSP4 + vc6MFC环境下开发和测试通过,使用前要注册ODBC数据源;数据源名称:wbQQuser;类型:ACCESS;文件名:wbQQuser.mdb,不明了之处请参看源程序,注释很清楚。

    2.多线程通信使用的全局变量导致函数耦合度较大。

    3.有些函数太长,导致功能不单一,内聚度降低。

    4.客户端点击传送文件后,应使该按钮无效,直到文件传送完毕或文件传送线程关闭再使之有效,如不使用全局变量有什么好办法实现。

    我认为学好一种技术爱好是最好的老师,交流是最好的方法,请高手赐教。

    QQ:2105629

    Email:lwb75@sina.com。

    志存高远,脚踏实地,生命不息,奋斗不止!

    展开全文
  • 由于因特网迅速流行,越来越多应用程序具备了在网上与其它程序通信的能力。从WIN95开始微软把网络功能融... 一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点连接,而本文在VC6.0环境下,运用了了
          

        由于因特网的迅速流行,越来越多的应用程序具备了在网上与其它程序通信的能力。从WIN95开始微软把网络功能融进了它的操作系统,使得应用程序网络通信能力更为普及。因此,微软的TCP/IP协议也就成为网络应用程序基于的首选协议。

        一般采用TCP/IP协议的应用程序只实现了单用户与服务器间点对点的连接,而本文在VC6.0的环境下,运用了了多线程以及共享数据结构技术,不仅实现了多用户与服务器间的连接,而且解决了多用户间信息互发问题----依靠服务器的转发功能。通过本文的阐述,希望能对那些需要编写多用户网络通信程序的读者以启发。

    一、技术概述

    1.1 基于TCP/IP的通信技术

        基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。下面简要地讲一下设计思路(VC6.0下):

        第一部分 服务器端
          一、创建服务器套接字(create)。
          二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。
          三、接受来自用户端的连接请求(accept)。
          四、开始数据传输(send/receive)。
          五、关闭套接字(closesocket)。

        第二部分 用户端
          一、创建用户套接字(create)。
          二、与远程服务器进行连接(connect),如被接受则创建接收进程。
          三、开始数据传输(send/receive)。
          四、关闭套接字(closesocket)。

        通过以上设计思路,我们可以创建一个简单的面向连接的单用户程序。下面,将介绍多线程技术,以使程序支持多用户。

    1.2 多线程技术

        我们可以把线程看成是一个进程(执行程序)中的一个执行点,每个进程在任何给定时刻可能有若干个线程在运行。一个进程中的所有线程共享该进程中同样的地址空间,同样的数据和代码,以及同样的资源。进程中每个线程都有自己独立的栈空间,和其它线程分离,并且不可互相访问。每个线程在本进程所占的CPU时间内,要么以时间片轮换方式,要么以优先级方式运行。如果以时间片轮换方式运行,则每个线程获得同样的时间量;如果以优先级方式运行,则优先级高的线程将得到较多的时间量,而优先级低的线程只能得到较少的时间量。方式的选择主要取决于系统时间调度器的机制以及程序的实时性要求。

        现在,运用多线程技术就可以实现对多用户的支持。即在服务器端,使接收来自用户端的连接请求(accept)这步无限循环,每接收一个用户请求,产生两个线程(send和receive线程),用来管理服务器与该用户的通信任务。下面,运用共享数据结构技术,就可以实现本问所要解决的关键技术---服务器转发技术。

    1.3 共享数据结构技术

        同一进程中的多个线程共存于单一的线性地址空间,因此,在多线程间共享数据结构是非常容易且方便的。但必须注意的是,对数据结构的访问必须是多线程互斥的,否则数据任意更改将导致不可预料的结果。本文所阐述的服务器转发技术也就是通过共享数据结构实现线程间的互相通信。

    二、实现方案

        整个系统分为三个相关的程序,即注册/登陆服务器、通信服务器以及用户程序。其中,注册/登陆服务器负责用户的注册、登陆以及数据库管理;通信服务器负责完成数据转发以及共享数据结构的管理;用户端则完成注册、登陆和通信功能。为什么要把服务器分为两部分呢?主要是考虑到服务器的用户容量问题,以及对通信服务器的保护,只有在通过验证后,用户在能与通信服务器连接。

        由此可见,整个系统通信任务的实现还是很复杂的。用户端首先必须注册自己,等待注册成功;然后根据自己的注册信息进行服务器登陆,登陆成功后才能与通信服务器连接,进行用户间通信。

        注册/登陆服务器接收到用户端的信息后,首先判断是注册信息还是登陆信息。如果是注册信息,则将该数据按预定的格式写入数据库,然后返回注册成功的消息,期间有任何异常产生,服务器都会返回注册失败消息,提示用户重新注册;如果是登陆信息,则从数据中提取用户名和ID与数据库中的内容进行比较,如果该用户存在,则返回登陆成功消息,反之,返回登陆失败消息。

        通信服务器所完成的主要功能是数据转发,这是通过与图中的共享数据结构进行交互完成的。服务器接收到用户端发来的消息后,提取消息的一部分与共享数据结构存储的内容进行比较,确定所要转发的对象,最后通过多线程及其通信机制完成数据转发。 下面,我们将分三部分来讨论系统的具体实现过程。

    三、具体实施

    3.1 注册/登陆服务器

        注册/登陆服务器程序是基于对话框的,该程序使用I/O端口56789与用户端连接。
        首先,在对话框初始化的同时完成网络初始化,即执行Init_net()函数,代码(不完整)如下:

     BOOL CServerDlg::Init_net()
    {网络初始化///
        addrLen=sizeof(SOCKADDR_IN);
        status=WSAStartup(MAKEWORD(1, 1), &Data);
        ………
        memset(&serverSockAddr, 0, sizeof(serverSockAddr));
    
    /*以下指定一个与某个SOCKET连接本地或远程地址*/
    
        serverSockAddr.sin_port=htons(PORT);
        serverSockAddr.sin_family=AF_INET;
        serverSockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
        serverSocket=socket(AF_INET, SOCK_STREAM, 0);//初始化SOCKET
        ………
    
        status=bind(serverSocket,(LPSOCKADDR)&serverSockAddr,sizeof(serverSockAddr)); //将SOCKET与地址绑定
        ………
        status=listen(serverSocket, 5); //开始监听
        ………
        return true;
    } 

        接着按下RUN键开始服务器功能,执行Reg_Load()函数,使服务器始终处于等待连接状态,但这样也使该线程始终阻塞。当有用户连接时,该函数创建一个任务用于处理与用户及数据库的事务。具体任务函数略(详见原始代码文件)。

     void CServerDlg::Reg_Load()
    {   
    	while(1)
    	{
    		CWinThread*  hHandle;
    		clientSocket=accept(serverSocket,(LPSOCKADDR)&clientSockAddr,&addrLen); //等待连接,阻塞
    		hHandle=AfxBeginThread(talkToClient,(LPVOID)clientSocket);//有连接时,创建任务
    	        ………
    	}
    } 

        任务函数在接收到消息时,要对数据库进行操作,由于数据库较简单,采用ODBC连接ACCESS数据库(将netuser.mdb在ODBC数据管理器中安装成同名数据源)具体代码略。

    3.2 通信服务器

        通信服务器是本程序实现的关键,它运用共享数据结构技术及多线程技术,通过I/O端口56790与用户端连接,实现了数据转发功能。首先,程序初始化网络Init_net(),接着当用户连接到服务器时,创建接收线程和发送线程,这样就可以实现数据转发。最后,当用户断开连接时,服务器关闭与他的连接,并结束相应的线程。

        下面我们来看一下本程序中的共享数据结构的具体内容与使用方法以及多线程的相关内容与实现。

    ● 共享数据结构

        本程序的共享数据结构一共有两个,即socket_info和send_info。前者包含了所有登陆用户的一些基本资料,后者则包含了当前服务器接收到的用户端所发送的信息资料。详细内容及注释如下:

     struct socket_info  
    {
        SOCKET s_client;                    //用户的SOCKET值
        u_long client_addr;             //用户网络地址
        CString pet;                        //用户昵称
        CWinThread* thread;             //为该用户创建的发送线程对象的指针
    };
    
    struct send_info
    {
        CString data;                   //用户端发送的数据内容(经过编辑)
        CWinThread* thread;             //需要发送数据的任务指针
    }; 

        在程序中,定义两个全局变量,用来在线程间共享:

     send_info info_data; CList<socket_info,socket_info&>s_info; 

        每当有用户连接到服务器,服务器就将用户端的一些信息以socket_info结构体的形式存入s_info列表中;而当服务器接收到用户端发送过来的数据时,就将数据格式化后存入结构体info_data,通过与结构体列表比较,确定需要恢复的发送线程(所有发送线程在创建时都被挂起)。这样,服务器就准确地转了发数据。

    ●多线程

        每当服务器上有用户连接成功,服务器都会为其创建两个线程:接收线程(RecvData)和发送线程(SendData),并且接收线程在创建后处于可执行状态,而发送线程则阻塞,等待服务器将其唤醒。这两个线程都执行一个无限循环的过程,只有当通信出现异常或用户端关闭连接时,线程才被自身所结束,并且,这两个线程一定是同时生成,同时结束的。很显然,每个连接产生两个线程,使得数据转发变的简单,但同时又使得服务器的任务加重。因此,用户端的连接数量有所限制,视服务器软、硬件能力而定。

        同时,由于多线程对结构体info_data都需要操作,所以线程间必须同步。这儿,我定义了互斥量CMutex m_mutex,用它的方法Lock()和Unlock()来完成同步。

    我们首先来看一下接收线程(RecvData):(不完整代码)

     UINT RecvData(void* cs)
    {   
    	SOCKET clientSocket=(SOCKET)cs;
    	while(1)
    	{
    		numrcv=recv(clientSocket, buffer, MAXBUFLEN, NO_FLAGS_SET);
    		buffer[numrcv]=''/0'';
    		if(strcmp(buffer,"Close!")!=0)  //不是接收的“Close”数据
    		{
    			…………
    		        for(i=0;i<count;i++)
    		        {
    				if(po!=NULL)
    				{
    					s1=s_info.GetNext(po);
    					if(s1.pet.Compare(petname)==0)      //比较昵称是否一样
    					{
    						m_mutex.Lock();    //互锁
    						info_data.data=pos;
    						info_data.thread=s1.thread;
    						m_mutex.Unlock();  //解锁
    					}
    					s1.thread->ResumeThread(); //恢复发送相应的线程
    					break;
    				}
    			}
    		}
    		else
    		{
    			…………
    			if(clientSocket==s1.s_client)
    			{
    				m_mutex.Lock(); //互锁
    				info_data.data=buffer;
    				m_mutex.Unlock();           //解锁
    				s1.thread->ResumeThread();  //恢复发送相应的线程
    				s_info.RemoveAt(po1);       //删除该用户信息
    				break;
    			}
    			………
    			goto aa;
    		}
    	}
    	aa: closesocketlink((LPVOID)clientSocket);          //关闭连接
    	AfxEndThread(0,true);                           //结束本线程
    	return 1;
    } 

        接下来看一下发送线程(SendData):(不完整代码)

     UINT SendData(void* cs)
    {
    	SOCKET clientSocket=(SOCKET)cs;
    	while(1)
    	{
    		if(info_data.data!="Close!")
    		{
    			m_mutex.Lock();               //互锁
    			numsnd=send(clientSocket,info_data.data,
    			info_data.data.GetLength(),NO_FLAGS_SET); //发送数据
    			now=info_data.thread;
    			m_mutex.Unlock();             //解锁
    			now->SuspendThread();         //自身挂起
    		}
    		else
    		{   goto bb; }
    
    	}
    	bb: closesocketlink((LPVOID)clientSocket);    //关闭连接
    	AfxEndThread(0,true);                         //结束本线程
    	return 1;
    } 

    3.3 用户端

        很显然,用户端不用考虑多线程,网络连接技术也比较成熟,因此在通信方面没有什么难题。但是,用户端是面向实际用户的,所以,不论是界面还是功能都必须友好。就像大多数软件的更新一样,界面友好度的提高以及功能的完善往往是放在首位的。由此可见,单从总体设计与技术实现角度来讲,用户端的工作量是十分大的,并且设计较服务器端复杂得 多。我粗略总结了以下几条:

        ●与服务器通信格式兼容;

        ●操作简单、易用,有美观的界面及快捷键;

        ●准确地接收和传输数据;

        ●所有的数据记录与提取功能;

        ●多种消息接收提示方式,比如托盘图标(发送者头像)闪烁、声音提示等;

        根据以上内容,我设计了三个独立的对话框分别用来完成注册、登陆、通信功能,登陆和注册对话框与服务器的56789I/O端口连接,通信对话框与服务器的56790I/O端口连接,这样就很好地实现了注册登陆与通信的隔离,既能使服务器负载降低,同时又能保证一定的通信安全性。

        由于本部分不是主要内容,详细代码见程序。

    四、结束语

        通过以上阐述可以知道,本系统分为服务器端和用户端,服务器端又分为注册/登陆服务器和通信服务器,通过通信服务器的转发功能实现了局域网内的多用户通信功能。本文运用了多线程技术和共享数据结构技术实现了通信服务器的转发功能,使一般基于TCP/IP的网络应用程序得到了发展。本系统已经在我实验室的局域网(一台服务器,二十台客户机)运行通过。

    展开全文
  • 计算机网络:局域网协议

    千次阅读 2020-05-24 14:43:12
    根据计算机网络拓扑结构,可将网络分为总线型、树型、...局域网中传输数据基本单元为“帧”,当采用不同的局域网通信协议时,其中具体数据帧格式也会不同,目前常见数据帧格式包括PPP帧和MAC帧。所有的局域网

    根据计算机网络的拓扑结构,可将网络分为总线型、树型、星型、环型和网状型五种类型。常见的局域网组网方式包括令牌环、光纤分布数字接口和以太网等。

    一、概述

    在不同类型的网络拓扑结构中,网络设备的连接方式、传输介质的选择、网络的可靠性及可扩展性均存在差异。在组建网络时,通常需要综合网络功能和性能需求、环境状况和投入成本等因素,以确定所采用的网络拓扑方案。

    局域网中传输数据的基本单元为“帧”,当采用不同的局域网通信协议时,其中具体的数据帧格式也会不同,目前常见的数据帧格式包括PPP帧和MAC帧。所有的局域网通信协议都需要解决帧定界、透明传输和差错检测这三个基本问题。

    常见的局域网组网方式包括令牌环、光纤分布数字接口(FDDI,Fiber Distributing Data Interface)和以太网等。

    1.令牌环

    令牌环网络的拓扑结构为环型拓扑,它使用同轴电缆作为传输介质,抗干扰性强。在令牌环网络中,使用一种被称为令牌的特殊数据帧进行访问控制,令牌循环遍历环路中的每台主机,主机在接收到令牌后才能发送数据。这种网络的结构固定,不便于网络拓展和增加主机,且维护令牌需要付出额外代价,在令牌损坏后,整个网络将不能正常工作。

    2.光纤分布数字接口

    光纤分布数字接口FDDI的网络拓扑结构同样为环型拓扑,采用光纤作为传输介质,有着较高的网络带宽。FDDI组网方式只支持采用光缆或5类电缆作为传输介质,这种组网方式成本较高,多用于连接多个局域网的骨干网。

    3.以太网

    以太网相对于前两种方式具有结构简单、成本低的特点,在实际应用中也更为常见。以太网使用双绞线作为传输介质,最大覆盖半径可达数百米之内。随着以太网技术的发展,其数据传输速率可达到从10Mbps到100Gbps不等。

    二、PPP帧与MAC帧

    数据链路层的常用信道有两种,即点对点信道和广播信道。

    在点对点信道中,发送端仅发送数据给接收端,是一种一对一的通信方式。

    在广播信道中,发送端将数据发送给连接至信道的多个设备,由于信道被多个设备共享,数据碰撞概率高,故需要通过网络协议来进行协调。

    1.点对点信道

    PPP(Point-to-Point Protocol)协议只提供无比特差错的数据传输服务,是目前使用点对点信道的网络中应用最广泛的数据链路层协议。

    终端用户通过因特网服务提供商(ISP,Internet Service Provider)所提供的数据链路层协议,获得相应的网络访问服务,中国电信和网通等ISP均采用PPP协议为终端用户提供通信服务。PPPoE(Point-to-Point Protocol over Ethernet)协议及PPPoA(Point-to-Point Protocol over ATM)协议是由PPP协议衍生而来的两个协议, 目前家庭常用的ADSL宽带上网方式就是采用了PPPoE协议技术。

    PPP协议规定的数据帧格式如图2-11所示,数据帧首部包含4个字段,尾部包含2个字段。首部的第一个字段和尾部的第二个字段均为标志字段,起到帧定界符的作用。标志字段Flag被规定为十六进制数0x7E,转化成二进制数即为01111110。在PPP帧的首部,标志字段之后依次为地址字段和控制字段,地址字段规定为0xFF,控制字段规定为0x03。首部最后一个字段长度为2字节,为协议字段,表示数据部分所对应的网络层协议。若协议字段值为0x0021,则表示数据部分为IP数据报;若协议字段值为0xC021,则表示数据部分为链路控制协议LCP的数据。尾部第一个字段即帧校验序列,用于检测数据比特差错。

    图2-11 PPP数据帧格式

    为保证数据的透明传输,PPP协议对异步和同步这两种传输方式分别采用了字符填充法和比特填充法。使用异步传输时,PPP协议定义转义字符为0x7D,在RFC1662文档中定义了相应的字符填充方法;在同步传输中,则采用“零”比特填充法实现透明传输。

    2.广播信道

    数据链路层被拆分为两个子层:逻辑链路控制LLC子层和介质访问控制MAC子层。MAC子层负责与接入到传输介质相关的所有工作,LLC子层的作用被逐渐淡化。

    以太网MAC帧的格式具有两种标准,一种是以太网V2标准(DIX Ethernet V2),另一种是IEEE 802.3标准,其中以太网V2标准的MAC帧应用最为广泛。

    如图2-12所示为MAC帧的格式示意图,前两个长度为6字节的字段分别代表目的地址和源地址,第三个字段长度为2字节,用于表示上一层协议的类型。若第三个字段值为0x0800,则表示上一层使用IP协议,数据部分为IP数据报。数据部分的长度在46~1500字节。从图2-12中可以看出,物理层所传输的数据比MAC帧多8个字节,前7个字节用于实现接收端与发送端时钟频率的同步,第8个字节为帧开始定界符,标识MAC帧的开始。

    图2-12 MAC帧格式

    这里的MAC地址由IEEE802.3规定的扩展唯一标识EUI-48(EUI,Extended Unique Identifier)表示,它是一个由48位二进制表示的字符串,又称为物理地址、硬件地址。此地址由24位公司ID(也称为制造商ID)和24位扩展ID(也称为网卡ID)组成。公司ID被唯一指派给每个网络适配器的制造商;扩展ID在装配时被唯一指派给每个网络适配器。两者组合,即可生成全球唯一的48位的MAC地址。

    三、CSMA/CD协议

    CSMA/CD协议是以太网所采用的一种监听避免“碰撞”的协议,该协议工作在OSI模型数据链路层的MAC子层,是一种“争用”型的半双工介质访问控制协议,包含以下几个要素。

    1.多点接入

    CSMA/CD协议的应用环境为总线型网络,网络中的主机以多点接入的方式连接在总线上。

    2.载波监听

    CSMA/CD协议要求发送端在发送数据前对总线进行监听,若监听到其他计算机在发送数据,则等待一段时间后,确认总线空闲时才开始发送数据。

    3.“碰撞”监听

    在数据发送过程中需保持对总线上信号的监听,根据信号电平的变化幅度即可判断是否发生了“碰撞”。一旦监听到“碰撞”,就立即停止数据发送,等待一段时间后再次尝试重发,直至发送成功。

    四、虚拟局域网

    虚拟局域网(VLAN,Virtual Local network)是一种将局域网设备从逻辑上划分为多个网段,从而实现虚拟工作组的交换技术。

    在典型的局域网中,通常一个工作组属于同一个网段,多个逻辑工作组之间通过网桥或者路由器交换数据,逻辑工作组受到节点所在物理位置的限制。

    虚拟局域网使用交换机,以软件的方式实现逻辑工作组的划分与管理,不受物理位置的限制而组建的局域网。

    如图2-13所示为一个虚拟局域网,一个VLAN可以包含一台或多台交换机,如图中椭圆形区域所示,有3个VLAN。PC-1、PC-2、PC-3、PC-4在同一个VLAN中,但连接到不同的交换机。同时,一个VLAN中的所有主机也可以连接到同一台交换机上,如PC-5与PC-6连接到Switch A。

    图2-13 虚拟局域网

    不同虚拟局域网的组网方法,主要表现在对虚拟局域网成员的定义上,通常有四种:交换机端口号、MAC地址、网络层地址和IP广播组等方式。虚拟局域网不同划分方法的优缺点如表2-8所示。

    表2- 8虚拟局域网不同划分方法的优缺点

    划分方法

    优点

    缺点

    交换机端口

    可简单定义VLAN成员,将交换机端口定义为相应的VLAN组 当用户改变端口时,需要重新进行配置

    MAC地址

    当用户物理位置改变时,VLAN不用重新配置 当交换机初始化时,所有的用户都必须进行配置

    网络层地址

    根据协议类型划分,方便网络管理 效率低,检查每个数据包的网络层地址需要消耗处理时间

    IP广播组

    灵活性好,且较易通过路由器进行扩展 不适合局域网,因其效率不高

    五、高速以太网

    高速以太网是指传输速率达到或超过100Mb/s的以太网,适用于较远距离的传输。

    1.快速以太网

    快速以太网(Fast Ethernet)的传输速率比传统10BASE-T以太网快10倍,数据传输速率达到100Mb/s。100BASE-T标准的快速以太网仍使用IEEE802.3的CSMA/CD(Carrier Sensing Multiple Access with Collision Detection)协议,并采用星型拓扑结构。用户只需更换一个适配器,再配上一个100Mb/s的集线器就可方便地由10BASE-T以太网直接升级到100Mb/s。目前的10Mb/s和100Mb/s以太网是使用无屏蔽双绞线布线的。

    100BASE-T以太网有3种与传输介质相关的标准,如表2-9所示。

    1)100BASE-TX:支持2对5类非屏蔽双绞线(UTP)或2对1类屏蔽双绞线(STP)。其中,一对双绞线用于发送,而另一对双绞线用于接收。因此,100BASE-TX工作在全双工模式,每个节点均可以同时以100Mb/s的速率发送与接收数据。

    2)100BASE-T4:支持4对3类或5类非屏蔽双绞线。其中,3对双绞线用于数据传输,每一对均以33.3 Mb/s的速率传送数据;而另一对用作冲突检测的接收信道。

    3)100BASE-FX:支持两芯的多模或单模光纤。其中,一根光纤用于数据发送,另一根光纤用于数据接收。100BASE-FX工作在全双工模式,主要用作高速主干网,从节点到集线器的距离可以达到2 km。

    表2- 9不同传输介质的100BASE-T以太网技术指标

    以太网标准

    100BASE-TX

    100BASE-T4

    100BASE-FX

    传输介质

    2对UTP-5

    2对STP-1

    4对UTP-3/5

    2芯光纤

    信号技术

    MLT-3

    MLT-3

    8B6T,NTZ

    4B5B,NRZI

    数据速率

    100Mb/s

    100Mb/s

    100Mb/s

    100Mb/s

    最大段长

    100m

    100m

    100m

    200m

    网络跨度

    200m

    200m

    200m

    400m

    2.吉比特以太网

    吉比特以太网的数据传输速率是快速以太网的10倍,可达到1Gb/s。吉比特以太网保留了传统10BASE-T以太网的基本特征,具有相同的帧格式和类似的组网方法。

    吉比特以太网定义了基于双绞线的物理层标准1000BASE-T和基于光纤通道的物理层标准1000BASE-X:

    1)1000BASE-T,即IEEE 802.3ab,使用4对5类非屏蔽双绞线,双绞线长度可达100 m。

    2)1000BASE-X,即IEEE 802.3z,有以下3种有关传输介质的标准:

    (1)1000BASE-CX;

    (2)1000BASE-LX;

    (3)1000BASE-SX。

    IEEE 802.3z标准的吉比特以太网具有以下特点:

    1)支持全双工和半双工两种工作方式;

    2)在半双工方式下采用CSMA/CD协议,而在全双工方式下不采用该协议;

    3)向后兼容10BASE-T和100BASE-T技术。

    3.10吉比特以太网

    10吉比特以太网又称万兆以太网,使用IEEE 802.3以太网介质访问控制MAC协议。与传统的以太网标准相比,10吉比特以太网具有以下特征:

    1)只支持双工模式,不支持单工模式,而传统的以太网标准均支持单工/双工模式。

    2)由于传输速率高,10吉比特以太网只能使用光纤作为传输介质,而传统的以太网标准均支持同轴电缆。

    3)不支持CSMA/CD协议,因为该协议只适用于速率较慢的单工以太网。

    4)使用64B/66B和8B/10B两种编码方式,而传统以太网只使用8B/10B的编码方式。

    5)具有支持局域网和广域网的接口,且有效距离可达40 km,而传统的以太网只支持局域网应用,有效传输距离不超过5 km。

    100 Mb/s以太网、1 Gb/s以太网和10 Gb/s以太网均属于共享介质方式的高速以太网。这里,介绍一些其他类型的高速以太网。

    1)光纤分布式数据接口。光纤分布式数据接口(FDDI,Fiber Distributed Data Interface)是一个使用光纤作为传输介质的令牌环网,使用共享介质方式。其主要具有以下几个特点:

    (1)使用IEEE 802.5的单令牌环网MAC协议。

    (2)兼容IEEE 802标准的局域网。

    (3)数据传输速率为100 Mb/s,联网的节点数不超过1000,环路长度为100 km。

    (4)使用双环结构,提高容错能力。

    (5)可以使用多模或单模光纤。

    2)高性能并行接口。高性能并行接口(HIPPI,High-Performance Parallel Interface)是一个美国国家标准协会标准,主要用于连接外围设备、处理器和巨型机等。

    HIPPI标准具有以下特点:

    (1)HIPPI提供点对点的接口,可以在两个设备间建立连接。

    (2)HIPPI工作在单工模式下,只能沿着一个方向传输数据,但是,两条单工通道可以组成一条双工通道。

    由于光纤价格昂贵,HIPPI在设计之初,只能采用双绞线作为数据传输的媒介。

    3)光纤通道。光纤通道(FC,Fibre Channel)是一种高速网络互联技术,主要用于连接计算机存储设备。

    光纤通道标准定义了三种不同的拓扑结构:点到点、仲裁环和交换网。

    六、宽带接入技术

    1.xDSL 技术

    xDSL技术是使用数字技术改造现有的模拟电话用户线,可以承载宽带业务。

    前缀x表示在数字用户线(DSL,Digital Subscriber Line)上实现的不同带宽方案。

    表2-10列出了xDSL的几种类型。其中ADSL(Asymmetric DSL)表示非对称数字用户线,HDSL(High speed DSL)表示高速数字用户线,SDSL(Single-line DSL)表示1对线的数字用户线,VDSL(Very high speed DSL)表示甚高速数字用户线。

    表2- 10 xDSL类型

    xDSL

    对称性

    下行带宽

    上行带宽

    极限传输距离

    ADSL

    不对称

    1.5 Mb/s

    64 kb/s

    4.6~5.5 km

    HDSL(1对线)

    对称

    768 Kb/s

    768 kb/s

    2.7~3.6 km

    SDSL

    对称

    1.5 Mb/s

    1.5 Mb/s

    3 km

    VDSL

    不对称

    52 Mb/s

    1.6~2.3 Mb/s

    0.3 km

    DSL(ISDN)

    对称

    160 Kb/s

    160 Kb/s

    4.6~5.5 km

    ADSL有上行和下行之分,其不对称的原因在于用户下载的数据量要大于上传的数据量。

    如图2-14所示,ADSL接入网包含数字用户线接入复用器(DSLAM,DSL Access Multiplexer)、用户线和用户设施如ADSL调制解调器(ATU-R,ADSLTransceiver Unit Remote Terminal End)、电话机和PC等。

    图2-14 ADSL接入网的组成

    ADSL的优势在于可以利用现有电话网中的用户线,而不需要重新布线。

    2.光纤同轴混合网

    光纤同轴混合网(HFC,Hybrid Fiber Coax)是一种居民宽带接入网,在原有有线电视网(CATV,Community Antenna TeleVision)的基础上发展而来。HFC网不仅能够传输电视信号,还能提供电话、数据等业务。

    HFC网采用光纤作为主干线路,并使用模拟光纤技术,如图2-15所示。头端发射出的光信号经过模拟光纤到达光纤节点,光纤节点将接收到的光信号转换为电信号,电信号经由光纤节点下游的同轴电缆传送到居民区。

    图2-15 HFC网的体系结构

    HFC网具有以下特点:

    1)HFC网以光纤作为主干线路。

    2)HFC网采用节点体系结构。

    3)HFC网具有更宽的频谱,且具有双向传输能力。

    4)用户接口盒。每个家庭都需要安装一个用户接口盒(UIB,User Interface Box)。

    3.FTTx 技术

    光纤通信是利用光波作为载波,以光纤作为传输媒体,将信息从源节点传送至目的节点的一种通信方式。

    光纤通信具有容量大、传输距离远、抗干扰能力强、性能稳定和保密性能好等优点,已在各种不同的骨干网中得到了广泛应用。

    FTTx中x代表不同的技术。

    1)光纤到户(FTTH,Fiber To The Home)是家庭接入的解决方案,即将光纤一直铺设到用户家庭内。

    2)光纤到大楼(FTTB,Fiber To The Building)方案能解决一幢大楼有多个用户连接宽带的情况。

    3)光纤到路边(FTTC,Fiber To The Curb)是指将光纤铺设到离家庭或办公室1km以内的路边,从路边到单个用户,可以采用双绞线或同轴电缆,以星型结构连接。FTTC代替了普通旧式电话服务,只需通过一条线就可以完成电话、有线电视、多媒体和其他通信业务。

    FTTx还提供其他宽带接入方案,如光纤到节点或邻区(FTTN,Fiber To The Node/Neighborhood)、光纤到交换机(FTTE,Fiber To The Exchange)、光纤到小区(FTTZ,Fiber To The Zone)和光纤到办公室(FTTO,Fiber To The Office)等。

     

     

     

     

     

    IEEE 802

     

    IEEE 802也指IEEE标准中关于局域网和城域网的一系列标准。更确切的说,IEEE 802标准仅限定在传输可变大小的网络。

    系列标准

    IEEE 802系列标准是IEEE 802 LAN/MAN 标准委员会制定的局域网、城域网技术标准。其中最广泛使用的有以太网令牌环无线局域网等。这一系列标准中的每一个子标准都由委员会中的一个专门工作组负责。

    IEEE 802委员会成立于1980年2月,它的任务是制定局域网和城域网标准。IEEE 802中定义的服务和协议限定在OSI模型[OSI网络参考模型]的最低两层(即物理层数据链路层)。事实上,IEEE 802将OSI的数据链路层分为两个子层,分别是逻辑链路控制(Logical Link Control, LLC)和介质访问控制(Media Access Control, MAC),如下所示:

    · 数据链路层

    · 逻辑链路控制子层

    · 介质访问控制子层

    · 物理层

    IEEE 802系列标准是IEEE 802 LAN/MAN 标准委员会制定的局域网、城域网技术标准。其中最广泛使用的有以太网令牌环无线局域网等。这一系列标准中的每一个子标准都由委员会中的一个专门工作组负责。

    802委员会前有20多个分委员会。

    IEEE 802现有标准

    IEEE 802.1 :局域网体系结构、寻址、网络互联和网络

    IEEE 802.1A:概述和系统结构

    IEEE 802.1B:网络管理和网络互连

    IEEE 802.2 :逻辑链路控制子层(LLC)的定义。

    IEEE 802.3 :以太网介质访问控制协议 (CSMA/CD)及物理层技术规范 [1]  

    IEEE 802.4 :令牌总线网(Token-Bus)的介质访问控制协议及物理层技术规范。

    IEEE 802.5 :令牌环网(Token-Ring)的介质访问控制协议及物理层技术规范。

    IEEE 802.6 :城域网介质访问控制协议DQDB (Distributed Queue Dual Bus 分布式队列双总线)及物理层技术规范。

    IEEE 802.7 :宽带技术咨询组,提供有关宽带联网的技术咨询。

    IEEE 802.8 :光纤技术咨询组,提供有关光纤联网的技术咨询。

    IEEE 802.9 :综合声音数据的局域网(IVD LAN)介质访问控制协议及物理层技术规范。

    IEEE 802.10:网络安全技术咨询组,定义了网络互操作的认证和加密方法。

    IEEE 802.11无线局域网(WLAN)的介质访问控制协议及物理层技术规范。

    IEEE 802.11,1997年,原始标准(2Mbit/s,播在2.4GHz)。

    IEEE 802.11a,1999年,物理层补充(54Mbit/s,播在5GHz)。

    IEEE 802.11b,1999年,物理层补充(11Mbit/s播在2.4GHz)。

    IEEE 802.11c,符合802.1D的媒体接入控制层桥接(MAC Layer Bridging)。

    IEEE 802.11d,根据各国无线电规定做的调整。

    IEEE 802.11e,对服务等级(Quality of Service, QoS)的支持。

    IEEE 802.11f,基站的互连性(IAPP,Inter-Access Point Protocol),2006年2月被IEEE批准撤销。

    IEEE 802.11g,2003年,物理层补充(54Mbit/s,播在2.4GHz)。

    IEEE 802.11h,2004年,无线覆盖半径的调整,室内(indoor)和室外(outdoor)信道(5GHz频段)。

    IEEE 802.11i,2004年,无线网络的安全方面的补充。.

    IEEE 802.11j,2004年,根据日本规定做的升级。

    IEEE 802.11l,预留及准备不使用。

    IEEE 802.11m,维护标准;互斥及极限。

    IEEE 802.11n,更高传输速率的改善,基础速率提升到72.2Mbit/s,可以使用双倍带宽40MHz,此时速率提升到150Mbit/s。支持多输入多输出技术(Multi-Input Multi-Output,MIMO)。

    IEEE 802.11k,该协议规范规定了无线局域网频谱测量规范。该规范的制订体现了无线局域网络对频谱资源智能化使用的需求。

    IEEE 802.11p,这个通信协定主要用在车用电子的无线通信上。它设置上是从IEEE 802.11来扩充延伸,来符合智能型运输系统(Intelligent Transportation Systems,ITS)的相关应用。

    IEEE 802.11ac,802.11n的潜在继承者,更高传输速率的改善,当使用多基站时将无线速率提高到至少1Gbps,将单信道速率提高到至少500Mbps。使用更高的无线带宽(80MHz-160MHz)(802.11n只有40MHz),更多的MIMO流(最多8条流),更好的调制方式(QAM256)。目前是草案标准(draft),预计正式标准于2012年晚些时间推出。Quantenna公司在2011年11月15日推出了世界上第一只采用802.11ac的无线路由器Broadcom公司于2012年1月5日也发布了它的第一支支持802.11ac的芯片。

    IEEE 802.11ae-2012

    IEEE 802.12 : [1]  [2-3]  需求优先的介质访问控制协议(100VG AnyLAN)。

    IEEE 802.13 :(未使用 )【不吉利的数字,没有人愿意使用它---查自《计算机网络-Andrew S. Tanebaum》 Page 63 - 1.6.2 国际标准领域中最有影响的组织】

    IEEE 802.14:采用线缆调制解调器(Cable Modem)的交互式电视介质访问控制协议及网络层技术规范。

    IEEE 802.15:采用蓝牙技术的无线个人网(Wireless Personal Area Networks,WPAN)技术规范。

    IEEE 802.15.1:无线个人网络。

    IEEE 802.15.4:低速无线个人网络

    IEEE 802.16:宽带无线连接工作组,开发2~66GHz的无线接入系统空中接口

    IEEE 802.17:弹性分组环 (Resilient Packet Ring,RPR)工作组,制定了单性分组环网访问控制协议及有关标准。

    IEEE 802.18:宽带无线局域网技术咨询组(Radio Regulatory)。

    IEEE 802.19:多重虚拟局域网共存(Coexistence)技术咨询组。

    IEEE 802.20:移动宽带无线接入( Mobile Broadband Wireless Access ,MBWA)工作组,制定宽带无线接入网的解决 。

    IEEE 802.21:媒介独立换手(Media Independent Handover)。

    IEEE 802.22: [4]  无线区域网(Wireless Regional Area Network)

    IEEE 802.23:紧急服务工作组 (Emergency Service Work Group)

    展开全文
  • 一种基于FPGA的无线局域网接入实现 长沙国防科技大学电子科学与工程学院 刘 伟 王 杉 魏急波 无线局域网是目前通信领域研究的一个热点,其MAC层协议采用的是一种载波侦听多址接入/冲突避免(CSMA/CA)的方式。...
  • 为减小中心交换机的通信压力,针对跨交换机通信,设计了带热备份路由协议(HSRP)的双中心交换机通信方案。在仿真和实验中将添加了热备份路由协议的双交换机网状拓扑与传统单交换机星形网络拓扑结构进行对比,结果...
  • 从全球市场看,WPA和即将推出无线局域网的高级安全标准IEEE 802.11i等协议将成为今年业界关注热点。 无线局域网产业是目前整个数据通信技术领域当中最为快速发展产业之一。无线局域网解决方案在部分场合作为...
  • 作者Email: zhh@httc.com.cn 随着计算机、网络通信技术发展,采用蓝牙技术组建无线局域网,不仅在办公条件不完善时能发挥作用,而且在临时增删办公点和移动性办公业务方面也有着独特优势。文中对蓝牙技术做了...
  • 网络简介  作为典型企业网架构,本单位网络拓扑... 我们知道,对于在Internet和Intranet网络上,使用TCP/IP协议时每台主机必须具有独立IP地址,有了IP地址主机才能与网络上其它主机进行通讯。随着网络应用大
  • 摘要:在深入研究IEEE 802.11 标准和无线局域网有关安全协议的基础上提出了改进安全无线局域网(SWLAN) 模型,及其基于嵌入式硬件系统实现方案。 SWLAN 采用接入控制方法,通过在MAC(medium access cont rol)...
  • 任务目标 聊天器采用客户端/服务器(C/S)模式; 客户端利用UDP与服务器相连,客户端与客户端之间通过UDP相互通信;...服务器与客户端间、客户端之间交互采用控制台方式或GUI窗口方式均可; 完成情...

    任务目标

    1. 聊天器采用客户端/服务器(C/S)模式;
    2. 客户端利用UDP与服务器相连,客户端与客户端之间通过UDP相互通信;
    3. 服务器端具有服务器端口设置维护客户端个人信息,记录客户端状态,分配账号等;
    4. 客户端具有服务器地址及端口设置,用户注册,用户登陆,添加好友和删除好友,查看好友信息,给好友发送消息等功能;
    5. 服务器与客户端间、客户端之间的交互采用控制台方式或GUI窗口方式均可;

    完成情况

    采用了客户器/服务器模式,实现了基于UDP客户端之间的相互通信,其优点有:在服务器端具有维护客户端个人信息,记录客户端状态,分配账号,服务器地址和端口的配置等。客户端上也实现了,地址及端口的设置用户注册和用户登录,添加好友及删除好友,查看好友是否在线,给好友发送消息等。但是不足的是,我们没有设计GUI窗口界面没有更加美观,而是用简洁的代码直接在运行中显示菜单栏;没有做出我们理想中的黑名单,我们本意上是打算实现黑名单功能,设置成黑名单的人不能发消息给我,除了黑名单的都可以发,只允许好友发,这个打算因为能力有限,并没有实现;接收缓冲区有延迟,处理不及时,需要先接收完上次数据才可以继续接受数据;群聊功能没有实现,只能私聊单个客户端对客户端的通信;发送和接受不能以多线程的方式同时进行,消息需要我们主动去接受。

    运行效果图

    服务器菜单:
    1098476-20190421153832071-1917555880.png

    服务器端口配置:
    1098476-20190421153840964-1854952362.png

    查看当前所有账户:

    1098476-20190421153846750-1574118302.png

    1098476-20190421153851471-1466895025.png

    开启服务器:
    1098476-20190421153858172-635800942.png

    客户端菜单:
    1098476-20190421153904398-123673070.png

    修改IP和端口号:
    1098476-20190421153911857-448332231.png

    注册账号:
    1098476-20190421153917700-1025302449.png

    登录:
    1098476-20190421153926855-1561315503.png

    查看好友列表(空):
    1098476-20190421153932167-1384520861.png

    ID1003添加好友:
    1098476-20190421153938552-706898390.png

    查看1003和1004好友:

    1098476-20190421153945404-2138321027.png

    状态值为1为在线,0为离线:
    1098476-20190421153951248-274602517.png

    1004发送信息至1003:
    1098476-20190421153956656-399993029.png

    1003接受信息:
    1098476-20190421154001741-1391514360.png

    删除好友:

    1098476-20190421154007814-1443885254.png

    理论基础

    UDP是OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务, UDP在IP报文的协议号是17。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
    UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。
    客户端/服务器(C/S)模式结构的基本原则是将计算机应用任务分解成多个子任务,由多台计算机分工完成,即采用“功能分布”原则。客户端完成数据处理,数据表示以及用户接口功能;服务器端完成DBMS(数据库管理系统)的核心功能。这种客户请求服务、服务器提供服务的处理方式是一种新型的计算机应用模式。

    工作原理

    采用客户端/服务器(C/S)模式;客户端利用UDP与服务器连接,客户端与客户端之间通过UDP互相通讯;服务器端具有服务器端口设置,维护客户端个人信息,记录客户端状态,分配账号等功能。客户端具有服务器地址及端口设置,用户注册,用户登陆,添加好友和删除好友,查看好友信息,给好友发送消息等功能;服务器与客户端间、客户端之间的交互采用控制台方式方式。主要是用规定好的格式发送字符信息。

    核心代码

    服务器端:

    1098476-20190421154026679-97990041.png

    data.h
    #include <Winsock2.h>
    
    struct Friends
    {
        int id;
        int ava; //是否有效 1 有效 ,0 无效
    };
    
    struct User
    {
        int id;
        char name[15];
        int online; //是否在线 1 在线 ,0 离线
        char passwd[15];
        sockaddr_in addr;
        Friends friends[50];
    };
    
    server.h
    #include "data.h"
    #include <string>
    using namespace std;
    //#include "net.cpp"
    
    //net.cpp
    void serverStart(User user[], int po);
    
    //user.cpp
    void saveFile(User user[]);
    void readFile(User user[]);
    int login(User user[], char str[]);
    int reg(User user[], char str[]);
    string watch(User user[], char str[]);
    int delF(User user[], char str[]);
    int addF(User user[], char str[]);
    
    user.cpp
    #include "data.h"
    #include <string.h>
    #include <stdio.h>
    #include <string>
    using namespace std;
    //#include <stdio.h>
    
    //从文件读取
    void readFile(User user[])
    {
    }
    
    //保存到文件
    void saveFile(User user[])
    {
    }
    
    //用户登录判断
    int login(User user[], char str[])
    {
        int id;
        char passwd[15];
        //char passwd1[15];
        sscanf(str, "%d %[^'\0']", &id, passwd); //接收用户发送的id 和 密码
        //sprintf(passwd,"%s'\0'",passwd1);
        printf("登录判断:%d %s \n", id, passwd);
    
        //int size = sizeof(user) / sizeof(user[0]);
    
        for (int i = 0; user[i].id != -1; i++)
        {
            printf("当前 id : %d %s \n", user[i].id, user[i].passwd);
            if (user[i].id == id)
            { //id匹配判断
                printf("id == id : %d %s \n", user[i].id, user[i].passwd);
                if (strcmp(user[i].passwd, passwd) == 0)
                { //密码匹配判断
                    user[i].online = 1;
                    return i;
                }
                else
                    return -1;
                //return i;
            }
        }
        return -1;
    }
    
    //用户注册
    int reg(User user[], char str[])
    {
        //User p;
        //int id = 1000;
        char name[15];
        char passwd[15];
        sscanf(str, "%s %s", name, passwd); //读取用户名 密码
        for (int i = 0; user[i].id != -1; i++) //找到user表最后一个(id=-1)
        {
            /*  if(strcmp(User[i].id, id) == 0)
            {
                id++;
            }
            else break;*/
        }
    
        if (user[i].id == -1)
        { //新用户数据保存到user里
            user[i].id = user[i - 1].id + 1;
            user[i].online = 0;
            //user[i].name=name;
            //user[i].passwd=passwd;
            strcpy(user[i].name, name);
            strcpy(user[i].passwd, passwd);
            user[i].friends[0].id = -1;
            user[i + 1].id = -1;
            user[i + 1].friends[0].id = -1;
            return i;
        }
        return -1;
    }
    
    //查看用户好友列表
    string watch(User user[], char str[])
    {
        int id;
        //char buf[1024];
        string st = "好友列表: \n-------\n";
        sscanf(str, "%d", &id);
        printf("\n --%d \n", id);
        for (int i = 0; user[i].id != -1; i++)
        { //先找到用户再user的下标,再通过friends数组反向找好友的user下标,即可获取信息
            if (user[i].id == id)
            {
                printf("匹配到 %d \n", user[i].id);
                for (int j = 0; user[i].friends[j].id != -1; j++)
                {
                    printf("user[i].friends[j].id = %d \n", user[i].friends[j].id);
                    if (user[i].friends[j].ava != 0)
                    {
                        int k = 0;
                        for (k = 0; user[k].id != -1; k++)
                        {
                            if (user[k].id == user[i].friends[j].id)
                                break;
                        }
                        char temp[100];
                        sprintf(temp, "ID : %d 用户名: %s 状态: %d \n", user[k].id, user[k].name, user[k].online);
                        //itoa
                        //st = st + "ID : " + user[k].id + " 用户名: " + user[k].name + " 状态: " + user[k].online + "\n";
                        st = st + temp;
                    }
                }
                //st = st + '\0';
                //char* buf = (char*)st.data();
                //printf("%s",buf);
                //return buf;
                return st;
            }
        }
        return "error";
    }
    
    //删除好友
    int delF(User user[], char str[])
    {
        int Uid, Fid, uid, fid;
        sscanf(str, "%d %d", &uid, &fid);
    
        printf("delF: %d %d \n", uid, fid);
    
        for (Uid = 0; user[Uid].id != -1; Uid++)
        {
            if (user[Uid].id == uid)
            {
                break;
            }
        }
        //Uid--;
        //if(user[Uid].id==-1) return -1;
    
        for (Fid = 0; user[Fid].id != -1; Fid++)
        {
            if (user[Fid].id == fid)
            {
                break;
            }
        }
        //Fid--;
        //if(user[Fid].id==-1) return -1;
    
        //双向删除好友
        int i = 0;
        for (i = 0; user[Uid].friends[i].id != -1; i++)
        {
            if (user[Uid].friends[i].id == fid)
            {
                user[Uid].friends[i].ava = 0;
                break;
            }
        }
    
        for (i = 0; user[Fid].friends[i].id != -1; i++)
        {
            if (user[Fid].friends[i].id == uid)
            {
                user[Fid].friends[i].ava = 0;
                break;
            }
        }
    
        return 1;
    }
    
    //添加好友
    int addF(User user[], char str[])
    {
        int Uid, Fid, uid, fid;
        sscanf(str, "%d %d", &uid, &fid);
    
        printf("addF: %d %d \n", uid, fid);
    
        for (Uid = 0; user[Uid].id != -1; Uid++)
        {
            if (user[Uid].id == uid)
            {
                break;
            }
        }
        //Uid--;
        //if(user[Uid].id==-1) return -1;
    
        for (Fid = 0; user[Fid].id != -1; Fid++)
        {
            if (user[Fid].id == fid)
            {
                break;
            }
        }
        //Fid--;
        //if(user[Fid].id==-1) return -1;
    
        printf("Uid %d Fid %d \n", Uid, Fid);
    
        //双向添加好友
        printf("* 0 *\n");
        int i = 0;
        for (i = 0; user[Uid].friends[i].id != -1; i++)
        {
            if (user[Uid].friends[i].id == uid)
                return -1;
        }
        printf("* 1 *\n");
        if (user[Uid].friends[i].id == -1)
        {
            user[Uid].friends[i].id = fid;
            user[Uid].friends[i].ava = 1;
            user[Uid].friends[i + 1].id = -1;
            user[Uid].friends[i + 1].ava = 0;
        }
        printf("* 2 *\n");
        for (i = 0; user[Fid].friends[i].id != -1; i++)
        {
            if (user[Fid].friends[i].id == fid)
                return -1;
        }
        printf("* 3 *\n");
        if (user[Fid].friends[i].id == -1)
        {
            user[Fid].friends[i].id = uid;
            user[Fid].friends[i].ava = 1;
            user[Fid].friends[i + 1].id = -1;
            user[Fid].friends[i + 1].ava = 0;
        }
        printf("* 4 *\n");
        return 1;
    }
    
    net.cpp
    #include <Winsock2.h>
    #include <stdio.h>
    
    #include "server.h"
    //#include "data.h"
    
    void serverStart(User user[], int po)
    {
        //加载套接字库
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;
    
        wVersionRequested = MAKEWORD(1, 1);
    
        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0)
        {
            return;
        }
    
        if (LOBYTE(wsaData.wVersion) != 1 || //低字节为主版本
            HIBYTE(wsaData.wVersion) != 1) //高字节为副版本
        {
            WSACleanup();
            return;
        }
    
        printf("server is operating!\n\n");
        //创建用于监听的UDP套接字
        SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
    
        SOCKADDR_IN addrSrv; //定义sockSrv发送和接收数据包的地址
        addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
        addrSrv.sin_family = AF_INET;
        //addrSrv.sin_port = htons(6000);
        addrSrv.sin_port = htons(po);
    
        //绑定套接字, 绑定到端口
        bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
        //将套接字设为监听模式, 准备接收客户请求
    
        SOCKADDR_IN addrClient; //用来接收客户端的地址信息
        int len = sizeof(SOCKADDR);
        //char recvBuf[1024]; //收
        //char sendBuf[1024]; //发
        //char tempBuf[1024]; //存储中间信息数据
    
        while (1)
        {
            char *recvBuf = new char[1024]; //接收数据
            char *sendBuf = new char[1024]; //发送数据
            char *tempBuf = new char[1024]; //临时数据
            //等待并数据
            recvfrom(sockSrv, recvBuf, 1024, 0, (SOCKADDR *)&addrClient, &len);
    
            //sprintf(tempBuf,"%s say : %s",inet_ntoa(addrClient.sin_addr),recvBuf);
            //printf("info -> %s \n",tempBuf);
    
            char type;
            //char msg[1024];
            char *msg = new char[1024];
    
            //将收到的信息进行拆分判断
            sscanf(recvBuf, "%c %[^\n]", &type, msg);
            int res = 0;
    
            printf("消息类型: %c \n", type);
            //printf(" Msg: %s \n",msg);
    
            switch (type)
            {
            case 'L': //登录
                res = login(user, msg);
                if (res != -1)
                {
                    user[res].addr = addrClient; //保存
                    user[res].online = 1;
                    sendto(sockSrv, "登录成功", strlen("登录成功") + 1, 0, (SOCKADDR *)&addrClient, len);
                }
                else
                {
                    sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR *)&addrClient, len);
                }
                break;
            case 'S': //发送
            {
                int id, i, f = 0;
                char sendMsg[1024];
                sscanf(msg, "%d %s", &id, sendMsg); //拆分信息
                printf("id: %d", id);
                printf("msg: %s", sendMsg);
                for (i = 0; user[i].id != -1; i++)
                {
                    if (user[i].id == id)
                    { //匹配发送方id
                        if (user[i].online == 1)
                        { //对方在线
                            char newsend[1024];
                            sprintf(newsend, "%s -> %s \n", user[i].name, sendMsg);
                            sendto(sockSrv, newsend, strlen(newsend) + 1, 0, (SOCKADDR *)&user[i].addr, len);
                            sendto(sockSrv, "发送成功", strlen("发送成功") + 1, 0, (SOCKADDR *)&addrClient, len); //往发送方返回成功信息
                            f = 1;
                        }
                        else
                            sendto(sockSrv, "对方不在线", strlen("对方不在线") + 1, 0, (SOCKADDR *)&addrClient, len); //不在线
                    }
                }
                if (f == 0)
                    sendto(sockSrv, "对方不存在", strlen("对方不存在") + 1, 0, (SOCKADDR *)&addrClient, len); //不存在
                break;
            }
            case 'R': //注册
            {
                res = reg(user, msg);
                char buf[1024];
                sprintf(buf, "你的ID : %d , 你的名字 : %s , 你的密码 : %s\n", user[res].id, user[res].name, user[res].passwd);
                /*if(res!=-1){
                        user[res].addr = addrClient;
                        user[res].online = 1;
                    } else {
                        sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
                    }*/
                sendto(sockSrv, buf, strlen(buf) + 1, 0, (SOCKADDR *)&addrClient, len); //返回注册信息
                break;
            }
            case 'F': //好友功能
            {
                char type2, more[50];
                sscanf(msg, "%c %[^\n]", &type2, more);
                printf("-- %c %s --\n", type2, more);
                switch (type2)
                {
                case 'A': //添加好友
                    addF(user, more);
                    sendto(sockSrv, "add ok", strlen("add ok") + 1, 0, (SOCKADDR *)&addrClient, len);
                    break;
                case 'D': //删除好友
                    delF(user, more);
                    sendto(sockSrv, "del ok", strlen("del ok") + 1, 0, (SOCKADDR *)&addrClient, len);
                    break;
                case 'W': //查看好友
                    //char bufW[512];
                    string st = watch(user, more);
                    char *bufW = (char *)st.data();
                    //char* bufW = watch(user , more );
                    printf("%s", bufW);
                    sendto(sockSrv, bufW, strlen(bufW) + 1, 0, (SOCKADDR *)&addrClient, len);
                    break;
                }
            }
            break;
            case 'Q': //退出
            {
                int my = -1, i = 0;
                sscanf(msg, "%d", &my);
                for (i = 0; user[i].id != -1; i++)
                {
                    if (user[i].id == my)
                    {
                        user[i].online = 0; //下线
                    }
                }
                sendto(sockSrv, "退出成功", strlen("退出成功") + 1, 0, (SOCKADDR *)&addrClient, len);
                break;
            }
            default: //不是格式输入
                sendto(sockSrv, "请按格式输入", strlen("请按格式输入") + 1, 0, (SOCKADDR *)&addrClient, len);
                break;
            }
    
            /*      if('q' == recvBuf[0])
            {
                sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
                printf("Chat end!\n");
                break;
            }*/
    
            //sprintf_s(tempBuf,"%s say : %s",inet_ntoa(addrClient.sin_addr),recvBuf);
            /*      sprintf(tempBuf,"%s say : %s",inet_ntoa(addrClient.sin_addr),recvBuf);
            printf("%s\n",tempBuf);*/
    
            //发送数据
            /*      printf("Please input data: \n");
            gets(sendBuf);
            sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);*/
        }
        closesocket(sockSrv);
        WSACleanup();
    }
    
    
    server.cpp
    #include "server.h"
    #include <stdio.h>
    #include <string>
    #include <stdlib.h>
    #include <conio.h>
    using namespace std;
    //#include "data.h"
    
    int po = 8089;
    
    void PortConfig()
    {
        system("cls");
        printf("当前端口号 : %d ,请输入新端口号:", po);
        scanf("%d", &po);
        printf("新的端口号为 : %d\n回车返回", po);
        getchar();
        getchar();
    } //端口配置
    
    void Mainte(User user[])
    {
        system("cls");
        printf("1.查看信息 2.修改信息\n请输入:");
        char ch;
        ch = getch();
        system("cls");
        if ('1' == ch)
            for (int i = 0; user[i].id != -1; i++)
            {
                printf("第%d条记录->ID:%d 用户名:%s 密码:%s \n", i, user[i].id, user[i].name, user[i].passwd);
            }
        else
        {
            int id, i;
            char name[15];
            char passwd[15];
            printf("请输入id:");
            scanf("%d", &id);
            printf("请输入名字:");
            scanf("%s", name);
            printf("请输入密码:");
            scanf("%s", passwd);
    
            printf("你的输入: %d %s %s\n", id, name, passwd);
            for (i = 0; user[i].id != -1; i++)
            {
                if (user[i].id == id)
                {
                    system("cls");
                    printf("匹配到 %d", user[i].id);
                    //sprintf(user[i].name,"%s",name);
                    //sprintf(user[i].name,"%s",passwd);
                    //printf(" %s ",user[i].name);
                    //printf("原记录->ID:%d 用户名:%s 密码:%s \n",i,user[i].id,user[i].name,user[i].passwd);
                    strcpy(user[i].name, name);
                    strcpy(user[i].passwd, passwd);
                    //printf(" %s ",user[i].name);
                    printf("更改成功->ID:%d 用户名:%s 密码:%s \n", user[i].id, user[i].name, user[i].passwd);
                    break;
                }
            }
        }
        getchar();
        getchar();
    } //维护客户端个人信息
    
    void Start(User user[])
    {
        serverStart(user, po);
    } //开启服务器
    
    void Distr(User user[])
    {
        system("cls");
        char buf[50];
        int i;
        printf("请输入名字 密码:");
        scanf("%[^\n]", buf);
        i = reg(user, buf);
        if (i != -1)
        {
            printf("新注册帐号 -> ID:%d 用户名:%s 密码:%s \n", user[i].id, user[i].name, user[i].passwd);
        }
        else
            printf("创建失败!回车返回");
        getchar();
        getchar();
    } //分配账号
    
    void MenuShow()
    {
        system("cls");
        printf("***************************************************************************************************************\n");
        printf(" |>> Server <<| \n\n");
        printf(" |>> 1.服务器 端口 配置 <<| \n\n");
        printf(" |>> 2.维护客户端个人信息 <<| \n\n");
        printf(" |>> 3.开启 服务器 <<| \n\n");
        printf(" |>> 4.分 配 账 号 <<| \n\n");
        printf(" |>> 5.退 出 <<| \n\n");
        printf("***************************************************************************************************************\n");
    }
    
    void SMenu(User user[])
    {
        int f = 1;
        while (f)
        {
            MenuShow();
            printf("选择以上编号:");
            char i;
            i = getch();
            switch (i)
            {
            case '1':
            {
                PortConfig();
                break;
            }
    
            case '2':
            {
                Mainte(user);
                break;
            }
    
            case '3':
            {
                system("cls");
                Start(user);
                break;
            }
    
            case '4':
            {
                Distr(user);
                break;
            }
    
            case '5':
                f = 0;
                break;
    
            default:
            {
                printf("输入错误!");
                break;
            }
            }
        }
    } //服务器端菜单选择
    
    void main()
    {
        User user[50]; //初始化 user 表
    
        //user[0]=new User;
        user[0].id = 1001;
        user[0].online = 0;
        strcpy(user[0].name, "b");
        strcpy(user[0].passwd, "1001");
        user[0].friends[0].id = -1;
        //user[0].name = "b";
        //user[0].passwd = "1001";
        //printf("%s %s",user[0].name,user[0].passwd);
        //user[1]=new User;
        user[1].id = 1002;
        user[1].online = 0;
        strcpy(user[1].name, "c");
        strcpy(user[1].passwd, "1002");
        user[1].friends[0].id = -1;
        //user[1].name = "b";
        //user[1].passwd = "1001";
    
        //user[2]=new User;
        user[2].id = -1;
        user[2].online = 0;
        user[2].friends[0].id = -1;
    
        //serverStart(user,8089);
    
        SMenu(user);
    }
    

    客户端:

    1098476-20190421154042571-1757349743.png

    client.h
    void sendAll(char ipaddr[],int port);
    int sendM(SOCKET sockSrv,char ipaddr[],int port,char sendBuf[],sockaddr_in addrSrv);
    
    client.cpp
    #include <Winsock2.h>
    #include <stdio.h>
    #include <windows.h>
    #include <stdlib.h>
    #include <conio.h>
    
    #include "client.h"
    // 必须的头文件
    
    //程序入口
    int main()
    {
        char ipaddr[25] = "127.0.0.1";
        int port = 8089;
    
        /*  WORD wVersionRequested;
        WSADATA wsaData;
        int err;
        int id;
        wVersionRequested = MAKEWORD(1,1);
        err = WSAStartup(wVersionRequested, &wsaData);
        if(err != 0)
        {
            return 5;
        }
        if(LOBYTE(wsaData.wVersion) != 1 || 
            HIBYTE(wsaData.wVersion) != 1) 
        {
            WSACleanup();
            return 5;
        }
        printf("Client is operating!\n\n");
        SOCKET sockSrv = socket(AF_INET,SOCK_DGRAM,0);
    
        sockaddr_in addrSrv;
        addrSrv.sin_addr.S_un.S_addr = inet_addr(ipaddr);
        addrSrv.sin_family = AF_INET;
        addrSrv.sin_port = htons(port);*/
    
        int len = sizeof(SOCKADDR);
    
        while (1)
        {
            system("cls");
            printf("***************************************************************************************************************\n\n");
            printf(" |>> Client <<| \n\n");
            printf(" |>> 1.服务器地址和端口配置 <<| \n\n");
            printf(" |>> 2.帐号注册 <<| \n\n");
            printf(" |>> 3.用户登录 <<| \n\n");
            printf(" |>> 4.退出 <<| \n\n");
            printf("***************************************************************************************************************\n\n");
            printf("请输入:");
            char ch;
            ch = getch();
            switch (ch)
            {
            case '1': //修改配置
            {
                char ipA[15];
                int po;
                system("cls");
                printf("请输入IP:");
                scanf("%s", ipA);
                printf("请输入端口:");
                scanf("%d", &po);
                strcpy(ipaddr, ipA);
                port = po;
                printf("修改成功");
                getchar();
                getchar();
            }
            break;
            case '2': //添加用户
            {
    
                WORD wVersionRequested;
                WSADATA wsaData;
                int err;
                int id;
    
                wVersionRequested = MAKEWORD(1, 1);
    
                err = WSAStartup(wVersionRequested, &wsaData);
                if (err != 0)
                {
                    return 5;
                }
    
                if (LOBYTE(wsaData.wVersion) != 1 || //低字节为主版本
                    HIBYTE(wsaData.wVersion) != 1) //高字节为副版本
                {
                    WSACleanup();
                    return 5;
                }
    
                printf("Client is operating!\n\n");
                //创建用于监听的套接字
                SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
    
                sockaddr_in addrSrv;
                addrSrv.sin_addr.S_un.S_addr = inet_addr(ipaddr);
                addrSrv.sin_family = AF_INET;
                addrSrv.sin_port = htons(port);
    
                int len = sizeof(SOCKADDR);
    
                system("cls");
                printf("请输入用户名 密码:");
                char buf[100], msg[100];
                scanf("%[^\n]", buf);
                sprintf(msg, "R %s", buf);
                sendM(sockSrv, ipaddr, port, msg, addrSrv);
                getchar();
                getchar();
                break;
            }
            case '3': //登录
                system("cls");
                sendAll(ipaddr, port);
                break;
            case '4':
            default:
                return 0;
            }
        }
    }
    
    
    send.cpp
    #include <Winsock2.h>
    #include <stdio.h>
    #include <windows.h>
    #include <stdlib.h>
    #include <conio.h>
    // 必须的头文件
    
    /*
    struct Args{
    int s; void *buf; int len; unsigned int flags; struct sockaddr *from; int *fromlen;
    };
    
    DWORD WINAPI ThreadFunc(LPVOID lp);
    */
    
    int sendM(SOCKET sockSrv, char ipaddr[], int port, char sendBuf[], sockaddr_in addrSrv)
    {
        //加载套接字库
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;
    
        wVersionRequested = MAKEWORD(1, 1);
    
        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0)
        {
            return -1;
        }
    
        if (LOBYTE(wsaData.wVersion) != 1 || //低字节为主版本
            HIBYTE(wsaData.wVersion) != 1) //高字节为副版本
        {
            WSACleanup();
            return -1;
        }
    
        printf("Client is operating!\n\n");
        //创建用于监听的套接字
        //SOCKET sockSrv = socket(AF_INET,SOCK_DGRAM,0);
    
        /*sockaddr_in addrSrv;
        addrSrv.sin_addr.S_un.S_addr = inet_addr(ipaddr);
        addrSrv.sin_family = AF_INET;
        addrSrv.sin_port = htons(port);*/
    
        int len = sizeof(SOCKADDR);
    
        char *recvBuf = new char[1024];
        //char *sendBuf=new char[1024];
        char *tempBuf = new char[1024];
    
        //scanf("%[^\n]",sendBuf);
        //printf("%s",sendBuf);
    
        if ('!' != sendBuf[0])
            sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrSrv, len);
        //等待并数据
        int res = 0;
        res = recvfrom(sockSrv, recvBuf, 1024, 0, (SOCKADDR *)&addrSrv, &len);
    
        if (res == -1)
            printf("暂时无数据!\n");
        else
            printf("%s \n", recvBuf);
    
        if ('q' == recvBuf[0])
        {
            return 1;
            //sendto(sockSrv,"Q",strlen("Q")+1,0,(SOCKADDR*)&addrSrv,len);
            //printf("Chat end!\n");
        }
    
        //sprintf(tempBuf,"%s say : %s",inet_ntoa(addrSrv.sin_addr),recvBuf);
        //scanf(recvBuf,"%[^'\0']",tempBuf);
        //sprintf(tempBuf,"temp: \n %[^'\0']",recvBuf);
        //printf("temp : \n%s \n\n",tempBuf);
    
        //发送数据
    
        //closesocket(sockSrv);
        WSACleanup();
    
        return 0;
    }
    
    
    sendAll.cpp
    #include <Winsock2.h>
    #include <stdio.h>
    #include <windows.h>
    #include <stdlib.h>
    #include <conio.h>
    
    #include "client.h"
    // 必须的头文件
    
    /*
    struct Args{
    int s; void *buf; int len; unsigned int flags; struct sockaddr *from; int *fromlen;
    };
    
    DWORD WINAPI ThreadFunc(LPVOID lp);
    */
    
    void sendAll(char ipaddr[], int port)
    {
        //加载套接字库
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;
        int id;
    
        wVersionRequested = MAKEWORD(1, 1);
    
        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0)
        {
            return;
        }
    
        if (LOBYTE(wsaData.wVersion) != 1 || //低字节为主版本
            HIBYTE(wsaData.wVersion) != 1) //高字节为副版本
        {
            WSACleanup();
            return;
        }
    
        printf("Client is operating!\n\n");
        //创建用于监听的套接字
        SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
    
        sockaddr_in addrSrv;
        addrSrv.sin_addr.S_un.S_addr = inet_addr(ipaddr);
        addrSrv.sin_family = AF_INET;
        addrSrv.sin_port = htons(port);
    
        int len = sizeof(SOCKADDR);
    
        int f = 1;
        while (f)
        { //登录
            char buf[1024], msg[1024];
            printf("请输入ID:");
            scanf("%d", &id);
            printf("请输入密码:");
            //getch();
            scanf("%s", buf);
            sprintf(msg, "L %d %s", id, buf);
            f = sendM(sockSrv, ipaddr, port, msg, addrSrv); //向服务器发送登录信息
            if (f)
            {
                printf("登录失败!回车返回");
                getchar();
                getchar();
                return;
            }
            getchar();
            getchar();
        }
        f = 1;
        int op = 0;
        while (f)
        {
            char *recvBuf = new char[1024];
            char *sendBuf = new char[1024];
            char *tempBuf = new char[1024];
            char ch;
            //printf("Please input data: \n");
            printf("1.查看好友 2.发送信息 3.接收信息 4.添加好友 5.删除好友 6.退出: \n");
            //gets(sendBuf);
            ch = getch();
            char in[1024];
            switch (ch)
            {
            case '1': //查看好友
                sprintf(sendBuf, "F W %d", id);
                sendM(sockSrv, ipaddr, port, sendBuf, addrSrv);
                break;
            case '2': //发送信息
                printf("请输入:对方id 信息\n");
                scanf("%[^\n]", in);
                sprintf(sendBuf, "S %s", in);
                sendM(sockSrv, ipaddr, port, sendBuf, addrSrv);
                break;
            case '3': //接收信息
                sprintf(sendBuf, "!");
                sendM(sockSrv, ipaddr, port, sendBuf, addrSrv);
                break;
            case '4': //添加好友
            {
                int dstid;
                printf("请输入:对方id\n");
                scanf("%d", &dstid);
                sprintf(sendBuf, "F A %d %d", id, dstid);
                sendM(sockSrv, ipaddr, port, sendBuf, addrSrv);
                break;
            }
            case '5': //删除好友
            {
                int dstid;
                printf("请输入:对方id\n");
                scanf("%d", &dstid);
                sprintf(sendBuf, "F D %d %d", id, dstid);
                sendM(sockSrv, ipaddr, port, sendBuf, addrSrv);
                break;
            }
            default:
                f = 0;
                sprintf(sendBuf, "Q %d", id);
                sendM(sockSrv, ipaddr, port, sendBuf, addrSrv);
                break;
            }
            continue;
    
            //scanf("%[^\n]",sendBuf);
            //printf("%s",sendBuf);
    
            if ('!' != sendBuf[0])
                sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrSrv, len);
            //等待并数据
    
            if ('Q' == sendBuf[0])
            {
                sendto(sockSrv, "Q", strlen("Q") + 1, 0, (SOCKADDR *)&addrSrv, len);
                printf("Chat end!\n");
                break;
            }
    
            recvfrom(sockSrv, recvBuf, 1024, 0, (SOCKADDR *)&addrSrv, &len);
    
            if ('q' == recvBuf[0])
            {
                sendto(sockSrv, "Q", strlen("Q") + 1, 0, (SOCKADDR *)&addrSrv, len);
                printf("Chat end!\n");
                break;
                //printf("Chat end!\n");
            }
    
            printf("%s \n", recvBuf);
    
            //发送数据
        }
        closesocket(sockSrv);
        WSACleanup();
    }
    
    展开全文
  • 计算机局域网

    2020-11-05 20:17:21
    转载自: 尔雅网课,吉林大学计算机网络基础,讲师李晓峰的课程总结 ...根据所采用的MAC协议实现数据帧的封装和拆封,差错校验和相应的数据通信管理。 局域网工作在OSI模型的最低两层,即物理层和数.
  • 摘要:从研究CAN2.OB总线规范入手,介绍了CAN总线规范的硬件基础,分析了CAN总线报文格式,通过对报文标识符的分配,设计了应用于该系统的通信协议,并给出了软件设计流程,较好地解决了智能建筑监控系统通信过程中...
  • 多线程Socket 编程实现局域网通信 ... 采用多线程实现局域网内每个在线客户端信息监听。 客户端:  接受服务器发来信息,或发送信息给服务器, 再有服务器转发给其他在线客户端。 服务器代码...
  • 摘要:从研究CAN2.OB总线规范入手,介绍了CAN总线规范的硬件基础,分析了CAN总线报文格式,通过对报文标识符的分配,设计了应用于该系统的通信协议,并给出了软件设计流程,较好地解决了智能建筑监控系统通信过程中...
  • Qt实现简易局域网通信(一)

    千次阅读 2018-06-30 18:09:51
    开发环境:Windows10Qt版本:Qt5.11.0 此局域网通信使用是TCP传输控制协议采用客户端/服务器模式,即C/S模式,使用QTcpSocket编写客户端,QTcpServer编写服务器,通过对端口监听,一旦发现客户端连接请求,...
  • 红外线通信协议介绍

    2021-02-22 08:53:30
    基于红外线的传输技术最近几年有了很大发展。...红外数据协会(IRDA)成立后,为了保证不同厂商的红外产品能够获得最佳的通信效果,红外通信协议将红外数据通信所采用的光波波长的范围限定在850至900nm之内。IRD
  • 迄今为止,无线网只采用专用协议,因为IP协议对内存和带宽要求较高,要降低它运行环境要求以适应微控制器及低功率无线连接很困难。  基于IEEE 802.15.4实现IPv6通信的IETF 6LoWPAN[1]草案标准发布有望改变这一...
  • 广泛使用的家电遥控器几乎都是采用的红外线传输技术。作为无线局域网的传输方式,红外线方式的最大优点是不受无线电干扰,且它的使用不受国家无线管理委员会的限制。但由于红外线的波长较短,对障碍物的衍射能力差,...
  • 以太网和局域网的关系

    千次阅读 2017-02-21 16:32:59
    以太网是当今现有局域网采用的通用通信协议标准,组建于七十年代早期。Ethernet(以太网)是一种传输速率为10Mbps的常用局域网(LAN)标准。在以太网中,所有计算机被连接一条同轴电缆上,采用具有冲突检测的载波感应...
  • 链路层与局域网 网络层与链路层工作 网络层 ...通信路径上不同的链路是否采用不同的链路层协议。 链路层信道类型 广播链路 点对点链路 许多主机被连接到相同的通信信道(共享信道) **直接
  • 数据通信系统DCS是CBTC系统核心,CBTC系统大多采用成熟基于IEEE 802.11协议标准无 线局域网技术作为数字通信传输系统。本文利用OPNET Modeler 14.5仿真平台,对CBTC系统数据通信子 系统建模,以802.1lb...
  • 项目通讯采用UDP通讯技术,包括:建立主机时广播信息报,搜索主机时广播搜索包,主机反馈应答包,以及加入主机包括双方游戏过程中的通信信息包。本项目功能完整,界面美观。另外包含项目的界面流程设计文件和通讯...
  • 电子邮件传输是通过电子邮件简单传输协议(Simple Mail Transfer Protocol,简称SMTP)这一系统软件来完成,它是Internet下一种电子邮件通信协议。常见电子邮件协议有以下几种:SMTP(简单邮件传输协议)、...
  • 摘要:利用TI推出针对简单小型RF网络专有低功耗RF协议——SimpliciTI网络协议设计了一种无线数据采集系统方案,方案中采用CC1110芯片等组成了系统硬件中心控制器、路由节点和终端节点,并在此基础上进行软件...
  • 链路层是做什么

    2018-05-06 11:43:26
    局域网采用的通信协议标准,封装格式详见 RFC 894。以太网链路层协议:SLIP(Serial Line IP 串行线路IP)、PPP(点对点协议)什么是IEEE 802?IEEE制定的局域网和城域网通信协议标准,封装格式详见 RFC 1042。SLIP协议...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 427
精华内容 170
关键字:

局域网采用的通信协议