一、基本原理

有时候我们需要实现一个公共的模块,需要对多个其他的模块提供服务,最常用的方式就是实现一个Socket Server,接受客户的请求,并返回给客户结果。

这经常涉及到如果管理多个连接及如何多线程的提供服务的问题,常用的方式就是连接池和线程池,基本流程如下:

首先服务器端有一个监听线程,不断监听来自客户端的连接。

当一个客户端连接到监听线程后,便建立了一个新的连接。

监听线程将新建立的连接放入连接池进行管理,然后继续监听新来的连接。

线程池中有多个服务线程,每个线程都监听一个任务队列,一个建立的连接对应一个服务任务,当服务线程发现有新的任务的时候,便用此连接向客户端提供服务。

一个Socket Server所能够提供的连接数可配置,如果超过配置的个数则拒绝新的连接。

当服务线程完成服务的时候,客户端关闭连接,服务线程关闭连接,空闲并等待处理新的任务。

连接池的监控线程清除其中关闭的连接对象,从而可以建立新的连接。

二、对Socket的封装

Socket的调用主要包含以下的步骤:

调用比较复杂,我们首先区分两类Socket,一类是Listening Socket,一类是Connected Socket.

Listening Socket由MySocketServer负责,一旦accept,则生成一个Connected Socket,又MySocket负责。

MySocket主要实现的方法如下:

int MySocket::write(const char * buf, int length)
{
        int ret = 0;
        int left = length;
        int index = 0;
        while(left > 0)
        {
                ret = send(m_socket, buf + index, left, 0);
                if(ret == 0)
                        break;
                else if(ret == -1)
                {
                        break;
                }
                left -= ret;
                index += ret;
        }
        if(left > 0)
                return -1;
        return 0;
}

int MySocket::read(char * buf, int length)
{
        int ret = 0;
        int left = length;
        int index = 0;
        while(left > 0)
        {
                ret = recv(m_socket, buf + index, left, 0);
                if(ret == 0)
                        break;
                else if(ret == -1)
                        return -1;
                left -= ret;
                index += ret;
        }

return index;
}

int MySocket::status()
{
        int status;
        int ret;
        fd_set checkset;
        struct timeval timeout;

FD_ZERO(&checkset);
        FD_SET(m_socket, &checkset);

timeout.tv_sec = 10;
        timeout.tv_usec = 0;

status = select((int)m_socket + 1, &checkset, 0, 0, &timeout);
        if(status < 0)
                ret = -1;
        else if(status == 0)
                ret = 0;
        else
                ret = 0;
        return ret;
}

int MySocket::close()
{
        struct linger lin;
        lin.l_onoff = 1;
        lin.l_linger = 0;
        setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(lin));
        ::close(m_socket);
        return 0;
}

MySocketServer的主要方法实现如下:

int MySocketServer::init(int port)
{
        if((m_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        {
                return -1;
        }

struct sockaddr_in serverAddr;
        memset(&serverAddr, 0, sizeof(struct sockaddr_in));
        serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(port);

if(bind(m_socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
        {
                ::close(m_socket);
                return -1;
        }

if(listen(m_socket, SOMAXCONN) == -1)
        {
                ::close(m_socket);
                return -1;
        }

struct linger lin;
        lin.l_onoff = 1;
        lin.l_linger = 0;

setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(lin));
        m_port = port;
        m_inited = true;
        return 0;
}

MySocket * MySocketServer::accept()
{
        int sock;
        struct sockaddr_in clientAddr;
        socklen_t clientAddrSize = sizeof(clientAddr);
        if((sock = ::accept(m_socket, (struct sockaddr *)&clientAddr, &clientAddrSize)) == -1)
        {
                return NULL;
        }
        MySocket* socket = new MySocket(sock);
        return socket;
}

MySocket * MySocketServer::accept(int timeout)
{
        struct timeval timeout;
        timeout.tv_sec = timeout;
        timeout.tv_usec = 0;

fd_set checkset;
        FD_ZERO(&checkset);
        FD_SET(m_socket, &checkset);

int status = (int)select((int)(m_socket + 1), &checkset, NULL, NULL, &timeout);
        if(status < 0)
                return NULL;
        else if(status == 0)
                return NULL;

if(FD_ISSET(m_socket, &checkset))
        {
                return accept();
        }
}

三、线程池的实现

一个线程池一般有一个任务队列,启动的各个线程从任务队列中竞争任务,得到的线程则进行处理:list<MyTask *>  m_taskQueue;

任务队列由锁保护,使得线程安全:pthread_mutex_t m_queueMutex

任务队列需要条件变量来支持生产者消费者模式:pthread_cond_t m_cond

如果任务列表为空,则线程等待,等待中的线程个数为:m_numWaitThreads

需要一个列表来维护线程池中的线程:vector<MyThread *> m_threads

每个线程需要一个线程运行函数:

void * __thread_new_proc(void *p)
{
    ((MyThread *)p)->run();
    return 0;
}

每个线程由MyThread类负责,主要函数如下:

int MyThread::start()
{

pthread_attr_t  attr;
    pthread_attr_init(&attr);
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

int ret = pthread_create(&m_thread, &attr, thread_func, args);
    pthread_attr_destroy(&attr);

if(ret != 0)
        return –1;

}

int MyThread::stop()
{

int ret = pthread_kill(m_thread, SIGINT);

if(ret != 0)
        return –1;
}

int MyThread::join()

{

int ret = pthread_join(m_thread, NULL);

if(ret != 0)

return –1;

}

void MyThread::run()

{

while (false == m_bStop)

{

MyTask *pTask = m_threadPool->getNextTask();

if (NULL != pTask)

{

pTask->process();

}

}

}

线程池由MyThreadPool负责,主要函数如下:

int MyThreadPool::init()
{

pthread_condattr_t cond_attr;
    pthread_condattr_init(&cond_attr);
    pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
    int ret =  pthread_cond_init(&m_cond, &cond_attr);
    pthread_condattr_destroy(&cond_attr);

if (ret_val != 0)
        return –1;

pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    ret = pthread_mutex_init(&m_queueMutex, &attr);
    pthread_mutexattr_destroy(&attr);

if (ret_val != 0)
        return –1;

for (int i = 0; i< m_poolSize; ++i)
    {
        MyThread *thread = new MyThread(i+1, this);        
        m_threads.push_back(thread);
    }

return 0;
}

int MyThreadPool::start()
{
    int ret;
    for (int i = 0; i< m_poolSize; ++i)
    {        
       ret = m_threads[i]->start();
       if (ret != 0)
           break;       
    }

ret = pthread_cond_broadcast(&m_cond);

if(ret != 0)
        return –1;
    return 0;
}

void MyThreadPool::addTask(MyTask *ptask)
{
    if (NULL == ptask)
        return;

pthread_mutex_lock(&m_queueMutex);

m_taskQueue.push_back(ptask);

if (m_waitingThreadCount > 0)
        pthread_cond_signal(&m_cond);

pthread_mutex_unlock(&m_queueMutex);
}

MyTask * MyThreadPool::getNextTask()
{
    MyTask *pTask = NULL;

pthread_mutex_lock(&m_queueMutex);

while (m_taskQueue.begin() == m_taskQueue.end())
    {  
        ++m_waitingThreadCount;

pthread_cond_wait(&n_cond, &m_queueMutex);

--m_waitingThreadCount;       
    }

pTask = m_taskQueue.front();

m_taskQueue.pop_front();

pthread_mutex_unlock(&m_queueMutex);

return pTask;   
}

其中每一个任务的执行由MyTask负责,其主要方法如下:

void MyTask::process()

{

//用read从客户端读取指令

//对指令进行处理

//用write向客户端写入结果

}

四、连接池的实现

每个连接池保存一个链表保存已经建立的连接:list<MyConnection *> * m_connections

当然这个链表也需要锁来进行多线程保护:pthread_mutex_t m_connectionMutex;

此处一个MyConnection也是一个MyTask,由一个线程来负责。

线程池也作为连接池的成员变量:MyThreadPool * m_threadPool

连接池由类MyConnectionPool负责,其主要函数如下:

void MyConnectionPool::addConnection(MyConnection * pConn)
{

pthread_mutex_lock(&m_connectionMutex);

m_connections->push_back(pConn);

pthread_mutex_unlock(&m_connectionMutex);

m_threadPool->addTask(pConn);
}

MyConnectionPool也要启动一个背后的线程,来管理这些连接,移除结束的连接和错误的连接。

void MyConnectionPool::managePool()
{

pthread_mutex_lock(&m_connectionMutex);

for (list<MyConnection *>::iterator itr = m_connections->begin(); itr!=m_connections->end(); )
    {
        MyConnection *conn = *itr;        
        if (conn->isFinish())
        {
            delete conn;
            conn = NULL;
            list<MyConnection *>::iterator pos = itr++;
            m_connections->erase(pos);                         
        }
        else if (conn->isError())
        {

//处理错误的连接
            ++itr;
        }
        else
        {
            ++itr;
        }
    }

pthread_mutex_unlock(&m_connectionMutex);

}

五、监听线程的实现

监听线程需要有一个MySocketServer来监听客户端的连接,每当形成一个新的连接,查看是否超过设置的最大连接数,如果超过则关闭连接,如果未超过设置的最大连接数,则形成一个新的MyConnection,将其加入连接池和线程池。

MySocketServer *pServer = new MySocketServer(port);

MyConnectionPool *pPool = new MyConnectionPool();

while (!stopFlag)

{

MySocket * sock = pServer->acceptConnection(5);

if(sock != null)

{

if(m_connections.size > maxConnectionSize)

{

sock.close();

}

MyTask *pTask = new MyConnection();

pPool->addConnection(pTask);

}

}

面向连接的Socket Server的简单实现(简明易懂)的更多相关文章

  1. Windows socket之最简单的socket程序

    原文:Windows socket之最简单的socket程序 最简单的服务器的socket程序流程如下(面向连接的TCP连接 ): 1. WSAStartup(); 初始化网络库的使用. 2. soc ...

  2. python socket server源码学习

    原文请见:http://www.cnblogs.com/wupeiqi/articles/5040823.html 这里就是自己简单整理一下: #!/usr/bin/env python # -*- ...

  3. Java Socket Server的演进 (一)

    最近在看一些网络服务器的设计, 本文就从起源的角度介绍一下现代网络服务器处理并发连接的思路, 例子就用java提供的API. 1.单线程同步阻塞式服务器及操作系统API 此种是最简单的socket服务 ...

  4. 面向连接的socket数据处理过程以及非阻塞connect问题

    对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET)在读写数据之前必须建立连接,首先服务器端socket必须在一个客户端知道的地址进行监听,也就是创建socket之后 ...

  5. socket编程,简单多线程服务端测试程序

    socket编程,简单多线程服务端测试程序 前些天重温了MSDN关于socket编程的WSAStartup.WSACleanup.socket.closesocket.bind.listen.acce ...

  6. python socket 实现的简单http服务器

    预备知识: 关于http 协议的基础请参考这里. 关于socket 基础函数请参考这里. 关于python 网络编程基础请参考这里. 一.python socket 实现的简单http服务器   废话 ...

  7. 用python socket模块实现简单的文件下载

    server端: # ftp server端 import socket, os, time server = socket.socket() server.bind(("localhost ...

  8. python之路 socket、socket server

    一.socket socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制,取后一种意思.通常也 称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可 ...

  9. Socket编程的简单实现

    关于socket编程的简单实现,主要分成客户端.服务端两个部分.实现如下: 1.服务端代码如下,注意:server端要优先于client端启动 2.client端代码,以及启动后客户端和服务端之间的简 ...

随机推荐

  1. Java线程与多线程教程

    本文由 ImportNew - liken 翻译自 Journaldev.   Java线程是执行某些任务的轻量级进程.Java通过Thread类提供多线程支持,应用可以创建并发执行的多个线程. 应用 ...

  2. Qt之坐标系统

    简述 坐标系统是由QPainter类控制的,再加上QPaintDevice和QPaintEngine类,就形成了Qt的绘图体系. QPainter:用于执行绘图操作. QPaintDevice:二维空 ...

  3. web开发必须知道的javascripat工具

    1,JavaScript compressor and comparison tool 有许多工具可以帮助你压缩JavaScript代码,但是这个过程比较耗时,并且,对于某个特定的场景来说,很难分析出 ...

  4. 319. Bulb Switcher——本质:迭代观察,然后找规律

    There are n bulbs that are initially off. You first turn on all the bulbs. Then, you turn off every ...

  5. BZOJ1727 [Usaco2006 Open]The Milk Queue 挤奶队列

    贪心...我怎么不会QAQ[捂脸熊] 对于1.2两头牛,如果1号牛要排在2号牛前面才能时间更少,则 $$max(A_1 + B_1 + B_2, \ A_1 + A_2 + B_2) \le max( ...

  6. 25款漂亮的 WordPress 杂志主题

    WordPress是一个免费和开源博客工具和一个内容管理系统(CMS)基于 PHP 和 MySQL,它运行在一个 Web 托管服务.每个月的 WordPress 主题是由开发人员,其中用户喜欢使用 W ...

  7. CentOS查看软件源提供的软件版本命令

    yum list available第二列包更详细的说明:yum info packagename

  8. java面向对象编程——第二章 java基础语法

    第二章 java基础语法 1. java关键字 abstract boolean break byte case catch char class const continue default do ...

  9. BroadcastReceiver接收系统广播消息

    Android常用的广播Action常量: ACTION_TIME_CHANGED:系统时间被改变. ACTION_DATE_CHANGED:系统日期被改变. ACTION_TIMEZONE_CHAN ...

  10. 终于!Linaro 加盟 Zephyr 项目

    导读 为物联网构建实时操作系统的开源协作项目 Zephyr 项目宣布,Linaro 有限责任公司以白金会员的身份加盟该项目.Linaro是一家为 ARM 架构开发开源软件的协作工程组织,也是全球性机构 ...