精华内容
下载资源
问答
  • JAVA 网络编程服务器端例程,一个测试socket通信的比较好的例程
  • 电子书下载 : ...书中融入了大量的具体示例和游戏案例代码,读者可以直接使用这些代码,添加客户端,实现简单的通过对网络游戏架构,从而更加深入的了解网络游戏服务器端的编程技术。
  • 黑马程序员linux服务器开发网络编程配套文档,很好的参考资料
  • Linux网络编程-网络基础-socket编程-高并发服务器,非常详细的资料,值得你学习。
  • 一、服务器编程框架 模块 单个服务器程序 服务器机群 I/O处理单元 处理客户连接,读写网络数据 作为接入服务器,实现负载均衡 逻辑单元 业务进程或线程 逻辑服务器 网络存储单元 本地...

     

    一、服务器编程框架

    模块单个服务器程序服务器机群
    I/O处理单元处理客户连接,读写网络数据作为接入服务器,实现负载均衡
    逻辑单元业务进程或线程逻辑服务器
    网络存储单元本地数据库 、文件或缓存数据库服务器
    请求队列各单元之间的通信方式各服务器之间的永久TCP连接

            I/O处理单元是服务器管理客户连接的模块。主要完成以下工作:等待并接受新的客户连接,接受客户数据,将服务器响应数据返回给客户端。但是,数据的收发不一定在I/O处理单元中执行,也可能在逻辑单元中执行,具体在何处执行取决于事件处理模式。对于一个服务器机群来说,它实现负载均衡,从所有逻辑服务器中选取负荷最小的一台来为新客户服务。

            一个逻辑单元通常是一个线程或者进程。它分析并处理客户数据,然后将结果传递给I/O处理单元或者直接发送给客户端。对服务器机群而言,一个逻辑单元本身是一台逻辑服务器。服务器通常拥有多个逻辑单元,以实现对多个客户任务的并行处理。

    网络存储单元可以使数据库、缓存和文件,甚至是一台独立的服务器。但它不是必须的,比如ssh、telnet等登录服务就不需要这个单元。

           请求队列是各单元之间的通信方式的抽象。I/O处理单元收到客户请求时,需要以某种方式通知一个逻辑单元来处理该请求。同样,多个逻辑单元同时访问同一个存储单元时,也需要采用某种机制来协调处理竞态条件。请求队列通常实现为池的一部分。

    二、I/O模型

    1.阻塞I/O和非阻塞I/O

            阻塞I/O执行的系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。非阻塞I/O执行的系统调用总是立即返回,而不管事件是否已经发生,如果事件没有立即发生则返回-1,和出错情况一样,此时可根据errno来区分这两种情况。对accept/send和recv而言,事件未发生时errno通常被设置为EAGAIN或者EWOULDBLOCK;对于connect而言,errno则被设置成EINPROGRESS。非阻塞I/O一般和I/O通知机制一起使用,如I/O复用和SIGIO信号。

    2.I/O复用

           I/O复用是最常使用的I/O通知机制,指定是应用程序通过I/O复用函数向内核注册一组事件,内核通过I/O复用函数把其中就绪的事件通知给应用程序。Linux上常用的I/O复用函数是select、poll和epoll_wait。I/O复用函数本身是阻塞的,它们能提高程序效率的原因在于它们具有同时监听多个I/O事件的能力。

    3.四种I/O模型

           阻塞I/O、I/O复用、信号驱动I/O这三者都是同步I/O模型。

    I/O模型读写操作和阻塞阶段
    阻塞I/O程序阻塞与读写函数
    I/O复用程序阻塞与I/O复用系统调用,但可同时监听多个I/O事件。对I/O本身的读写操作是非阻塞的
    SIGIO信号·信号触发读写就绪事件,用户程序执行读写操作,程序没有阻塞阶段
    异步I/O内核执行读写操作并触发读写完成事件。程序没有阻塞阶段

    三、两种高效的事件处理模式

          服务器程序通常要处理三类事件:I/O事件、信号和定时事件。两种高效的事件处理模式:Reactor和Proactor。一般地,同步I/O模型常用于实现Reactor模式,异步I/O模型则用于实现Proactor模式。

    1.Reactor模式

            Reactor是一种这样的模式,它要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)。除此之外,主线程不作任何其他实质性工作。读写数据,接收新的连接,以及处理客户请求均在工作线程完成。

           使用同步I/O模型(以epoll_wait为例)实现的Reactor模式的工作流程是:

           (1)主线程往epoll内核事件表中注册socket读就绪事件;

           (2)主线程调用epoll_wait等待socket上有数据可读;

           (3)当socket上有数据可读时,epoll_wait通知主线程。主线程则将socket可读事件放入请求队列;

          (4)睡眠在请求队列上的某个工作线程被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件;

            (5)主线程调用epoll_wait等待socket可写;

           (6)当socket可写时,epoll_wait通知主线程,主线程将socket可写事件放入请求队列;

           (7)睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。

    2.Proactor模式

             Proactor模式将所有I/O操作都交给主线程和内核来处理,工作线程仅负责业务逻辑。

            使用异步I/O模型(以aio_read和aio_write为例)实现的Proactor模式工作流程是:

            (1)主线程调用aio_read函数想内核注册socket上读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序;

            (2)主线程继续处理其他逻辑;

            (3)当socket上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用;

            (4)应用程序预先定义好的处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求以后,调用aio_write函数想内核注册socket上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序;

            (5)主线程继续处理其他逻辑;

            (6)当用户缓冲区的数据被写入socket之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕;

            (7)应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭socket.

    3.两者的区别

            Reactor模式适用于耗时短的处理场景,同时接受多个服务请求,并且一次同步的处理它们的事件驱动程序。

            Proactor则适用于耗时长的处理场景,异步接受和同时处理多个服务器请求的事件驱动程序。

    四、两种高效的并发模式

             并发编程的目的是让程序“同时”执行多个任务。而并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法。

    1.半同步/半异步模式

            同步是指程序完全按照代码序列的顺序执行;异步是指程序的执行需要有系统事件来驱动,常见的系统事件包括中断、信号等。

            一种高效的半同步/半异步模式是指,主线程只管理监听socket,连接socket由工作线程来管理。主线程是异步的,工作线程也是异步的,这不是严格意义上的半同步/半异步.

     

    2.领导者/追随者模式

            领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听、分发并处理时间的一种模式。在任意时间点,程序都仅有一个领导者,它负责监听I/O事件,而其他线程则都是追随者,他们休眠在线程池中等待称为新的领导者。当前的领导者如果检测到I/O事件,首先要冲线程池中推选出新的领导者线程,然后处理I/O事件。此时,新的领导者等待新的I/O事件,而原来的领导者则处理I/O事件,二者实现了并发。

    五、提高服务器性能的其他建议

            高性能服务器要注意的几个方面:池、数据复制、上下文切换和锁。

    1.池

        池是一组资源的集合,这组资源在服务器启动之初就完全被创建好并初始化,这称为静态资源分配。当拂去其正式运行阶段,即开始处理客户请求的时候,如果需要相关资源,则可以直接从池中获取,无需动态分配。因为直接从池中获取资源比动态分配资源的速度要快的多,因为分配系统资源的系统调用都是很耗时的,涉及到用户态和内核态的来回切换。当服务器处理完一个客户连接以后,可以把相关资源放回池中,无需执行系统调用来释放资源。

            常见的池有内存池、进程池、线程池和连接池。

    (1)内存池

            内存池通常用于socket的接收缓冲和发送缓冲。内存池的大小可以根据情况进行分配,一种是预先分配好固定的内存池大小,另一种是根据情况动态扩大接收缓冲区。

    (2)进程池/线程池

            进程池和线程池都是用于并发的手段,当我们需要一个工作进程或工作线程来处理新到来的客户请求时,我们可以直接从进程的池或线程池中取得一个执行实体,而无须动态调用fork或pthread_create等函数来创建进程和线程。

    (3)连接池

            连接池通常用于服务器或服务器机群的内部永久连接。如每个逻辑单元都可能需要频繁访问本地的某个数据库。简单做法是:逻辑单元每次需要访问数据库的时候,就像数据库程序发起连接,而访问完毕后释放连接。这种做法效率太低。一种解决方案是使用连接池。连接池是服务器预先和数据库程序建立的一组连接的集合。当某个逻辑单元需要访问数据库时,它可以直接从连接池中取得一个连接的实体并使用之。待完成后再返回给连接池。

    2.数据复制

            尽量使用“零拷贝”函数,如sendfile()、tee()等,从而避免数据在用户空间和内核空间的来回拷贝,提高效率。

    3.上下文切换和锁

            不管是多进程还是多线程,数量都不应该太多,否则可能出现进程间或线程间切换占用大量的CPU时间,从而降低效率。

           并发程序中考虑的另外一个问题是共享资源的加锁保护,加的锁应粒度尽可能的小。

     

    补充:socket的基础API中,可能被阻塞的系统调用包括accept、send、recv和connect.

    展开全文
  • C++socket网络编程大全实战http服务器(支持php)视频培训教程概况:本课程会同时演示在linux和windows中的编程,课程中的线程和正则表达式都使用c++提供库。本课程包含了socket网络编程常用的所有特性,包括tcp、udp...
  • C++_面试题(服务器编程、网络编程),很全,很实用!
  • 网络编程之文件服务器网络编程之文件服务器。 说明:支持断点续传。
  • 网络游戏服务器编程

    2016-12-26 13:27:26
    电子书下载 : ...本书的主要内容:网络的基本原理、UNIX套接字编辑、Winsock编程、游戏服务器编程、游戏服务器编程开发模型、用于插件式游戏的基本模块的开发、网络程序库。
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • Qt网络编程实战之HTTP服务器视频课程配套资料 Qt网络编程实战之HTTP服务器资料
  • Linux网络编程 TCP/IP基础知识 涵盖socket epoll 多线程
  • c# 服务器与客户端编程 后果发挥感c# 服务器与客户端编程 后果发挥感
  • 网络游戏服务器编程.pdf 个人收集电子书,仅用学习使用,不可用于商业用途,如有版权问题,请联系删除!
  • C++SOCKET多线程网络编程实现多个客户端与服务器通信 #设计思路 通过在服务器端建立两个线程,主线程用来监听客户端的接入,当有新的客户端接入时,服务器为新的客户端建立服务线程,通过服务线程来实现服务器与...

    C++SOCKET多线程网络编程实现多个客户端与服务器通信

    #设计思路
    通过在服务器端建立两个线程,主线程用来监听客户端的接入,当有新的客户端接入时,服务器为新的客户端建立服务线程,通过服务线程来实现服务器与客户端的通信。
    #结果演示
    服务器端
    客户端1客户端2
    服务器端代码:

    #include"stdafx.h"
    #include <cstdio>
    #include<iostream>
    #include<string>
    #include<WinSock2.h>
    #pragma comment(lib,"ws2_32.lib")
    using namespace std;
    const int PORT = 8000;
    #define MaxClient 10
    #define MaxBufSize 1024
    #define _CRT_SECURE_NO_WARINGS
    //服务线程
    DWORD WINAPI ServerThread(LPVOID lpParameter){
    	SOCKET *ClientSocket=(SOCKET*)lpParameter;
    	int receByt=0;
    	char RecvBuf[MaxBufSize];
    	char SendBuf[MaxBufSize];
    	while(1){
    	receByt = recv(*ClientSocket,RecvBuf,sizeof(RecvBuf),0);
    	//buf[receByt]='\0';
    	if(receByt>0){
            cout<<"接收到的消息是:"<<RecvBuf<<"            来自客户端:"<<*ClientSocket<<endl;
    	   // cout<<receByt<<endl;
    	}
    	else
    	{
    	cout<<"接收消息结束!"<<endl;
    	break;
    	}
    	memset(RecvBuf,0,sizeof(RecvBuf));
    	cout<<"请输入要发送到客户端的信息:"<<endl;
    	gets(SendBuf);
    	int k=0;
    	k=send(*ClientSocket,SendBuf,sizeof(SendBuf),0);
    	if(k<0){
    	cout<<"发送失败"<<endl;
    	}
    	memset(SendBuf,0,sizeof(SendBuf));
    	}//while
    	closesocket(*ClientSocket);
    	free(ClientSocket);
    	return 0;
    }
    
    int main(){
    WSAData wsd;
    WSAStartup(MAKEWORD(2,2),&wsd);
    SOCKET ListenSocket = socket(AF_INET,SOCK_STREAM,0);
    SOCKADDR_IN ListenAddr;
    ListenAddr.sin_family=AF_INET;
    ListenAddr.sin_addr.S_un.S_addr=INADDR_ANY;//表示填入本机ip
    ListenAddr.sin_port=htons(PORT);
    int n;
    n=bind(ListenSocket,(LPSOCKADDR)&ListenAddr,sizeof(ListenAddr));
    if(n==SOCKET_ERROR){
    cout<<"端口绑定失败!"<<endl;
    return -1;
    }
    else{
    cout<<"端口绑定成功:"<<PORT<<endl;
    }
    int l =listen(ListenSocket,20);
    cout<<"服务端准备就绪,等待连接请求"<<endl;
    
    while(1){
    	//循环接收客户端连接请求并创建服务线程
    	SOCKET *ClientSocket = new SOCKET;
    	ClientSocket=(SOCKET*)malloc(sizeof(SOCKET));
    //接收客户端连接请求
    int SockAddrlen = sizeof(sockaddr);
    *ClientSocket = accept(ListenSocket,0,0);
    cout<<"一个客户端已连接到服务器,socket是:"<<*ClientSocket<<endl;
    CreateThread(NULL,0,&ServerThread,ClientSocket,0,NULL);
    }//while
    closesocket(ListenSocket);
     WSACleanup();
     return(0);
    }//main
    

    #客户端代码

    #include "Client.h"
    #include <cstdio>
    #include<iostream>
    #include<string>
    #include<WinSock2.h>
    #pragma comment(lib,"ws2_32.lib")
    using namespace std;
    const int PORT = 8000;
    #define MaxBufSize 1024
    #define _CRT_SECURE_NO_WARINGS
    int main(){
    WSADATA wsd;
    WSAStartup(MAKEWORD(2,2),&wsd);
    SOCKET SocketClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    SOCKADDR_IN  ClientAddr;
    
    ClientAddr.sin_family= AF_INET;
    ClientAddr.sin_addr.S_un.S_addr= inet_addr("127.0.0.1");
    ClientAddr.sin_port = htons(PORT);
    int n=0;
    n=connect(SocketClient,(struct sockaddr*)&ClientAddr,sizeof(ClientAddr));
    if(n==SOCKET_ERROR){
    cout<<"连接失败"<<endl;
    return -1;
    }
    cout<<"已经连接到服务器,可以向服务器发送消息了!"<<endl;
    char info[1024],SendBuff[MaxBufSize],RecvBuff[MaxBufSize];
    while(1){
    cout<<"请输入要发送的信息,按回车结束发送:"<<endl;
    gets(info);
    if(info[0]=='\0')
    	break;
    strcpy(SendBuff,info);
    memset(info,0,sizeof(info));
    int k=0 ;
    n=send(SocketClient,SendBuff,sizeof(SendBuff),0);
    memset(SendBuff,0,sizeof(SendBuff));
    if(k<0){
    cout<<"发送失败"<<endl;
    }
    Sleep(500);
    int n=0;
    n=recv(SocketClient,RecvBuff,sizeof(RecvBuff),0);
    if(n>0){
    cout<<"接收到来自服务器的消息为:"<<RecvBuff<<endl;
    }
    memset(RecvBuff,0,sizeof(RecvBuff));
    }
    closesocket(SocketClient);
    WSACleanup();
    return 0;
    
    
    
    }
    
    展开全文
  • 用c语言实现简单的socket网络编程TCP/IP客户服务器的连接,并实现两者直接的相互通信
  • 1.2 网络编程技术 ...按照前面的基础知识介绍,无论使用TCP方式还是UDP方式进行网络通讯,网络编程都是由客户端和服务器端组成。当然,B/S结构的编程中只需要实现服务器端即可。所以,下面介绍网络编程的步骤时...

    1.2 网络编程技术

    前面介绍了网络编程的相关基础知识,初步建立了网络编程的概念,但是实际学习网络编程还必须使用某种程序设计语言进行代码实现,下面就介绍一下网络编程的代码实现。

     

    1.2.1 网络编程步骤

    按照前面的基础知识介绍,无论使用TCP方式还是UDP方式进行网络通讯,网络编程都是由客户端和服务器端组成。当然,B/S结构的编程中只需要实现服务器端即可。所以,下面介绍网络编程的步骤时,均以C/S结构为基础进行介绍。

    说明:这里的步骤实现和语言无关,也就是说,这个步骤适用于各种语言实现,不局限于Java语言。

     

    1.2.1.1 客户端网络编程步骤

    客户端(Client)是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,无论客户端复杂还是简单,以及客户端实现的方式,客户端的编程主要由三个步骤实现:

    1、 建立网络连接

    客户端网络编程的第一步都是建立网络连接。在建立网络连接时需要指定连接到的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。

     

    2、 交换数据

    连接建立以后,就可以通过这个连接交换数据了。交换数据严格按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据给客户端,如果客户端不发送请求则服务器端就不响应。

    根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。

     

    3、 关闭网络连接

    在数据交换完成以后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。

    最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。

     

    1.2.1.2 服务器端网络编程步骤

    服务器端(Server)是指在网络编程中被动等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:

    1、 监听端口

    服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。

    这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。

     

    2、 获得连接

    当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端的信息,例如客户端IP地址等等,服务器端和客户端也通过该连接进行数据交换。

    一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。

     

    3、 交换数据

    服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换数序不同。

    其实,服务器端获得的连接和客户端连接是一样的,只是数据交换的步骤不同。

    当然,服务器端的数据交换也是可以多次进行的。

    在数据交换完成以后,关闭和客户端的连接。

     

    4、 关闭连接

    当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。

    其实服务器端编程的模型和呼叫中心的实现是类似的,例如移动的客服电话10086就是典型的呼叫中心,当一个用户拨打10086时,转接给一个专门的客服人员,由该客服实现和该用户的问题解决,当另外一个用户拨打10086时,则转接给另一个客服,实现问题解决,依次类推。

    在服务器端编程时,10086这个电话号码就类似于服务器端的端口号码,每个用户就相当于一个客户端程序,每个客服人员就相当于服务器端启动的专门和客户端连接的线程,每个线程都是独立进行交互的。

    这就是服务器端编程的模型,只是TCP方式是需要建立连接的,对于服务器端的压力比较大,而UDP是不需要建立连接的,对于服务器端的压力比较小罢了。

     

    1.2.1.3 小结

    总之,无论使用任何语言,任何方式进行基础的网络编程,都必须遵循固定的步骤进行操作,在熟悉了这些步骤以后,可以根据需要进行逻辑上的处理,但是还是必须遵循固定的步骤进行。

    其实,基础的网络编程本身不难,也不需要很多的基础网络知识,只是由于编程的基础功能都已经由API实现,而且需要按照固定的步骤进行,所以在入门时有一定的门槛,希望下面的内容能够将你快速的带入网络编程技术的大门。

     

     

    展开全文
  • 第一章:什么是计算机网络 第二章 :UNIX套接字(socket)编程 第三章:winsock编程 第四章:服务器程序开发模型 第五章:网络游戏服务器编程 第六章:插件式游戏基本模块
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • 网络资源修改下,实现服务器端与客户端的互相收发消息!
  • C# 网络编程-1-服务器

    2018-05-15 14:02:59
    C#语言编程,其中网络编程中,服务器部分基本介绍和编程实例
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • Java服务器高级编程.pdf
  • 《TCP IP网络编程

    热门讨论 2015-06-02 22:23:10
    《TCP/IP网络编程》针对网络编程初学者,面向具备C 语言基础的套接字网络编程学习者,适合所有希望学习Linux和Windows 网络编程的人。 第一部分主要介绍网络编程基础知识。此部分主要论述Windows和Linux平台网络编程...
  • Linux高性能服务器编程-高清-pdf

    千次下载 热门讨论 2014-10-18 09:55:37
    《Linux高性能服务器编程》是Linux服务器编程领域的经典著作,由资深Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐释了编写高性能Linux服务器应用的方法、技巧和思想...
  • Linux C++网络编程

    千人学习 2019-01-17 09:52:40
    这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。 这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪...
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • C语言之网络编程服务器和客户端)

    万次阅读 多人点赞 2017-08-18 20:27:36
    Linux网络编程 1、 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。 常用的TCP/IP协议的3种套接字类型如下所示。 (1)流套接字(SOCK_STREAM): ...

    Linux网络编程

    1、 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。

    常用的TCP/IP协议的3种套接字类型如下所示。

    (1)流套接字(SOCK_STREAM):

    流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission ControlProtocol)协议

    (2)       数据报套接字(SOCK_DGRAM):

    数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

    (3)       原始套接字(SOCK_RAW):(一般不用这个套接字)

    原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW

    2、 套接字基本函数:

    (1)      创建套接字:int socket(int family, int type, intprotocol);

    功能介绍:

          在Linux操作系统中,一切皆文件,这个大家都知道,个人理解创建socket的过程其实就是一个获得文件描述符的过程,当然这个过程会是比较复杂的。可以从内核中找到创建socket的代码,并且socket的创建和其他的listen,bind等操作分离开来。socket函数完成正确的操作是返回值大于0的文件描述符,当返回小于0的值时,操作错误。同样是返回一个文件描述符,但是会因为三个参数组合不同,对于数据具体的工作流程不同,对于应用层编程来说,这些也是不可见的。

    参数说明:

          从socket创建的函数可以看出,socket有三个参数,family代表一个协议族,比较熟知的就是AF_INET,PF_PACKET等;第二个参数是协议类型,常见类型是SOCK_STREAM,SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三个参数是具体的协议,对于标准套接字来说,其值是0,对于原始套接字来说就是具体的协议值。

    (2)      套接字绑定函数: intbind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

    功能介绍:

          bind函数主要应用于服务器模式一端,其主要的功能是将addrlen长度 structsockaddr类型的myaddr地址与sockfd文件描述符绑定到一起,在sockaddr中主要包含服务器端的协议族类型,网络地址和端口号等。在客户端模式中不需要使用bind函数。当bind函数返回0时,为正确绑定,返回-1,则为绑定失败。

    参数说明:

          bind函数的第一个参数sockfd是在创建socket套接字时返回的文件描述符。

          bind函数的第二个参数是structsockaddr类型的数据结构,由于structsockaddr数据结构类型不方便设置,所以通常会通过对tructsockaddr_in进行地质结构设置,然后进行强制类型转换成structsockaddr类型的数据,

     

    (3)      监听函数:int listen(int sockfd, int backlog);

    功能介绍:

          刚开始理解listen函数会有一个误区,就是认为其操作是在等在一个新的connect的到来,其实不是这样的,真正等待connect的是accept操作,listen的操作就是当有较多的client发起connect时,server端不能及时的处理已经建立的连接,这时就会将connect连接放在等待队列中缓存起来。这个等待队列的长度有listen中的backlog参数来设定。listen和accept函数是服务器模式特有的函数,客户端不需要这个函数。当listen运行成功时,返回0;运行失败时,返回值位-1.

    参数说明:

          sockfd是前面socket创建的文件描述符;backlog是指server端可以缓存连接的最大个数,也就是等待队列的长度。

     

    (4)      请求接收函数: int accept(int sockfd, structsockaddr *client_addr, socklen_t *len);

    功能介绍:

          接受函数accept其实并不是真正的接受,而是客户端向服务器端监听端口发起的连接。对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。返回大于0的文件描述符则表示accept成功,否则失败。

    参数说明:

          sockfd是socket创建的文件描述符;client_addr是本地服务器端的一个structsockaddr类型的变量,用于存放新连接的协议族,网络地址以及端口号等;第三个参数len是第二个参数所指内容的长度,对于TCP来说其值可以用sizeof(structsockaddr_in)来计算大小,说要说明的是accept的第三个参数要是指针的形式,因为这个值是要传给协议栈使用的。

    (5)客户端请求连接函数: intconnect(int sock_fd, struct sockaddr *serv_addr,int addrlen);

    功能介绍:

          连接函数connect是属于client端的操作函数,其目的是向服务器端发送连接请求,这也是从客户端发起TCP三次握手请求的开始,服务器端的协议族,网络地址以及端口都会填充到connect函数的serv_addr地址当中。当connect返回0时说明已经connect成功,返回值是-1时,表示connect失败。

    参数说明:

          connect的第一个参数是socket创建的文件描述符;第二个参数是一个structsockaddr类型的指针,这个参数中设置的是要连接的目标服务器的协议族,网络地址以及端口号;第三个参数表示第二个参数内容的大小,与accept不同,这个值不是一个指针。

          

          在服务器端和客户端建立连接之后是进行数据间的发送和接收,主要使用的接收函数是recv和read,发送函数是send和write。因为对于socket套接字来说,最终实际操作的是文件描述符,所以可以使用对文件进行操作的接收和发送函数对socket套接字进行操作。read和write函数是文件编程里的知识,所以这里不再做多与的赘述。

     

    3、 有了以上的知识,那么我们就可以编写一个简单的服务器和客户端了

    (1)       简易服务器:这个服务器只能与一个客户端相连接,如果有多个客户端就不能用这个服务器进行连接。

    代码:
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    
    #define PORT 9990   //端口号
    #define SIZE 1024   //定义的数组大小
    
    int Creat_socket()    //创建套接字和初始化以及监听函数
    {
    	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);   //创建一个负责监听的套接字
    	if(listen_socket == -1)
    	{
    		perror("socket");
    		return -1;
    	}
    	struct sockaddr_in addr;
    	memset(&addr, 0, sizeof(addr));
    	
    	addr.sin_family = AF_INET;  /* Internet地址族 */
        addr.sin_port = htons(PORT);  /* 端口号 */
        addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    	
    	int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));  //连接
    	if(ret == -1)
    	{
    		perror("bind");
    		return -1;
    	}
    	
    	ret = listen(listen_socket, 5);        //监听
    	if(ret == -1)
    	{
    		perror("listen");
    		return -1;
    	}
    	return listen_socket;
    }
    
    int wait_client(int listen_socket)
    {
    	struct sockaddr_in cliaddr;
    	int addrlen = sizeof(cliaddr);
    	printf("等待客户端连接。。。。\n");
    	int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);   //创建一个和客户端交流的套接字
    	if(client_socket == -1)
    	{
    		perror("accept");
    		return -1;
    	}
    	
    	printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr));
    	
    	return client_socket;
    }
    
    void hanld_client(int listen_socket, int client_socket)   //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
    {
    	char buf[SIZE];
    	while(1)
    	{
    		int ret = read(client_socket, buf, SIZE-1);
    		if(ret == -1)
    		{
    			perror("read");
    			break;
    		}
    		if(ret == 0)
    		{
    			break;
    		}
    		buf[ret] = '\0';
    		int i;
    		for(i = 0; i < ret; i++)
    		{
    			buf[i] = buf[i] + 'A' - 'a';
    		}
    		
    		printf("%s\n", buf);
    		write(client_socket, buf, ret);
    		
    		if(strncmp(buf, "end", 3) == 0)
    		{
    			break;
    		}
    	}
    	close(client_socket);
    }
    
    int main()
    {
    	int listen_socket = Creat_socket();
    	
    	int client_socket = wait_client(listen_socket);
    	
    	hanld_client(listen_socket, client_socket);
    	
    	close(listen_socket);
    	
    	return 0;
    }
    


    (2) 多进程并发服务器:该服务器就完全弥补了上一个服务器的不足,可以同时处理多个客户端,只要有客户端来连接它,他就能响应。在我们这个服务器中,父进程主要负责监听,所以在父进程一开始就要把父进程的接收函数关闭掉,防止父进程在接收函数处阻塞,导致子进程不能创建成功。同理,子进程主要负责接收客户端,并做相关处理,所以子进程在一创建就要把监听函数关闭,不然会导致服务器功能的紊乱。这个服务器有一个特别要注意的是,子进程在退出时会产生僵尸进程,所以我们一定要对子进程退出后进行处理。

    代码:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <sys/wait.h>
    
    #define PORT 9990
    #define SIZE 1024
    
    int Creat_socket()         //创建套接字和初始化以及监听函数
    {
    	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);      //创建一个负责监听的套接字  
    	if(listen_socket == -1)
    	{
    		perror("socket");
    		return -1;
    	}
    	struct sockaddr_in addr;
    	memset(&addr, 0, sizeof(addr));
    	
    	addr.sin_family = AF_INET;  /* Internet地址族 */
        addr.sin_port = htons(PORT);  /* 端口号 */
        addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    	
    	int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));    //连接
    	if(ret == -1)
    	{
    		perror("bind");
    		return -1;
    	}
    	
    	ret = listen(listen_socket, 5);   //监听
    	if(ret == -1)
    	{
    		perror("listen");
    		return -1;
    	}
    	return listen_socket;
    }
    
    int wait_client(int listen_socket)
    {
    	struct sockaddr_in cliaddr;
    	int addrlen = sizeof(cliaddr);
    	printf("等待客户端连接。。。。\n");
    	int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);     //创建一个和客户端交流的套接字
    	if(client_socket == -1)
    	{
    		perror("accept");
    		return -1;
    	}
    	
    	printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr));
    	
    	return client_socket;
    }
    
    void hanld_client(int listen_socket, int client_socket)    //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
    {
    	char buf[SIZE];
    	while(1)
    	{
    		int ret = read(client_socket, buf, SIZE-1);
    		if(ret == -1)
    		{
    			perror("read");
    			break;
    		}
    		if(ret == 0)
    		{
    			break;
    		}
    		buf[ret] = '\0';
    		int i;
    		for(i = 0; i < ret; i++)
    		{
    			buf[i] = buf[i] + 'A' - 'a';
    		}
    		
    		printf("%s\n", buf);
    		write(client_socket, buf, ret);
    		
    		if(strncmp(buf, "end", 3) == 0)
    		{
    			break;
    		}
    	}
    	close(client_socket);
    }
    
    void handler(int sig)
    {
    	
    	while (waitpid(-1,  NULL,   WNOHANG) > 0)
    	{
    		printf ("成功处理一个子进程的退出\n");
    	}
    }
    
    int main()
    {
    	int listen_socket = Creat_socket();
    	
    
    	signal(SIGCHLD,  handler);    //处理子进程,防止僵尸进程的产生
    	while(1)
    	{
    		int client_socket = wait_client(listen_socket);   //多进程服务器,可以创建子进程来处理,父进程负责监听。
    		int pid = fork();
    		if(pid == -1)
    		{
    			perror("fork");
    			break;
    		}
    		if(pid > 0)
    		{
    			close(client_socket);
    			continue;
    		}
    		if(pid == 0)
    		{
    			close(listen_socket);
    			hanld_client(listen_socket, client_socket);
    			break;
    		}
    	}
    	
    	close(listen_socket);
    	
    	return 0;
    }
    


    (3) 多线程并发服务器:上一个多进程服务器有一个缺点,就是每当一个子进程得到响应的时候,都要复制父进程的一切信息,这样就导致了CPU资源的浪费,当客户端有很多来连接这个服务器的时候,就会产生很多的子进程,会导致服务器的响应变得很慢。所以我们就想到了多线程并发服务器,我们知道线程的速度是进程的30倍左右,所以我们就用线程来做服务器。

    代码:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <pthread.h>
    
    
    #define PORT 9990
    #define SIZE 1024
    
    int Creat_socket()         //创建套接字和初始化以及监听函数
    {
    	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);      //创建一个负责监听的套接字  
    	if(listen_socket == -1)
    	{
    		perror("socket");
    		return -1;
    	}
    	struct sockaddr_in addr;
    	memset(&addr, 0, sizeof(addr));
    	
    	addr.sin_family = AF_INET;  /* Internet地址族 */
        addr.sin_port = htons(PORT);  /* 端口号 */
        addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    	
    	int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));    //连接
    	if(ret == -1)
    	{
    		perror("bind");
    		return -1;
    	}
    	
    	ret = listen(listen_socket, 5);   //监听
    	if(ret == -1)
    	{
    		perror("listen");
    		return -1;
    	}
    	return listen_socket;
    }
    
    int wait_client(int listen_socket)
    {
    	struct sockaddr_in cliaddr;
    	int addrlen = sizeof(cliaddr);
    	printf("等待客户端连接。。。。\n");
    	int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen);     //创建一个和客户端交流的套接字
    	if(client_socket == -1)
    	{
    		perror("accept");
    		return -1;
    	}
    	
    	printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr));
    	
    	return client_socket;
    }
    
    void hanld_client(int listen_socket, int client_socket)    //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母
    {
    	char buf[SIZE];
    	while(1)
    	{
    		int ret = read(client_socket, buf, SIZE-1);
    		if(ret == -1)
    		{
    			perror("read");
    			break;
    		}
    		if(ret == 0)
    		{
    			break;
    		}
    		buf[ret] = '\0';
    		int i;
    		for(i = 0; i < ret; i++)
    		{
    			buf[i] = buf[i] + 'A' - 'a';
    		}
    		
    		printf("%s\n", buf);
    		write(client_socket, buf, ret);
    		
    		if(strncmp(buf, "end", 3) == 0)
    		{
    			break;
    		}
    	}
    	close(client_socket);
    }
    
    int main()
    {
    	int listen_socket = Creat_socket();
    	
    	while(1)
    	{
    		int client_socket = wait_client(listen_socket);
    		
    		pthread_t id;
    		pthread_create(&id, NULL, hanld_client, (void *)client_socket);  //创建一个线程,来处理客户端。
    		
    		 pthread_detach(id);   //把线程分离出去。
    	}
    	
    	close(listen_socket);
    	
    	return 0;
    }
    

    (4)客户端:客户端相对于服务器来说就简单多了,客户端只需要创建和服务器相连接的套接字,然后对其初始化,然后再进行连接就可以了,连接上服务器就可以发送你想发送的数据了。
    代码:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    
    #define PORT 9990
    #define SIZE 1024
    
    int main()
    {
    	int client_socket = socket(AF_INET, SOCK_STREAM, 0);   //创建和服务器连接套接字
    	if(client_socket == -1)
    	{
    		perror("socket");
    		return -1;
    	}
    	struct sockaddr_in addr;
    	memset(&addr, 0, sizeof(addr));
    	
    	addr.sin_family = AF_INET;  /* Internet地址族 */
        addr.sin_port = htons(PORT);  /* 端口号 */
        addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    	inet_aton("127.0.0.1", &(addr.sin_addr));
    
    	int addrlen = sizeof(addr);
    	int listen_socket =  connect(client_socket,  (struct sockaddr *)&addr, addrlen);  //连接服务器
    	if(listen_socket == -1)
    	{
    		perror("connect");
    		return -1;
    	}
    	
    	printf("成功连接到一个服务器\n");
    	
    	char buf[SIZE] = {0};
    	
    	while(1)        //向服务器发送数据,并接收服务器转换后的大写字母
    	{
    		printf("请输入你相输入的:");
    		scanf("%s", buf);
    		write(client_socket, buf, strlen(buf));
    		
    		int ret = read(client_socket, buf, strlen(buf));
    		
    		printf("buf = %s", buf);
    		printf("\n");
    		if(strncmp(buf, "END", 3) == 0)     //当输入END时客户端退出
    		{
    			break;
    		}
    	}
    	close(listen_socket);
    	
    	return 0;
    }
    






    展开全文
  • 1.网络编程概要.mkv2.一个TCP的简单实验.mkv3.课程内容大纲.mkv4.回顾基础的Sockets API.mkv5.TTCP代码概览.mkv6.使用TTCP进行网络传输性能测试.mkv7.阻塞IO下的TTCP实验.mkv8.TCP自连接.mkv9.扩展练习.mkv10.时钟...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 445,272
精华内容 178,108
关键字:

服务器网络编程