通用异步 Windows Socket TCP 客户端组件的设计与实现

简介:

编写 Windows Socket TCP 客户端其实并不困难,Windows 提供了6种 I/O 通信模型供大家选择。但本座看过很多客户端程序都把 Socket 通信和业务逻辑混在一起,剪不断理还乱。每个程序都 Copy / Parse 类似的代码再进行修改,实在有点情何以堪。因此本座利用一些闲暇时光写了一个基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件和一个通用异步 Windows Socket TCP 客户端组件供各位看官参详参详,希望能激发下大家的灵感。本篇文章讲述客户端组件。闲话少说,我们现在步入正题。

  • 最重要的第一个问题:如何才能达到通用?

  答:很简单。

    1、限制组件的职能,说白了,通信组件的唯一职责就是接受和发送字节流,绝对不能参与上层协议解析等工作。不在其位不谋其政就是这个意思。

    2、与上层使用者解耦、互不依赖,组件与使用者通过接口方法进行交互,组件实现 ISocketClient 接口为上层提供操作方法;使用者通过 IClientSocketListener 接口把自己注册为组件的 Listener,接收组件通知。因此,任何使用者只要实现了 IClientSocketListener 接口都可以使用组件;另一方面,你甚至可以自己重新写一个实现方式完全不同的组件实现给使用者调用,只要该组件遵从 ISocketClient 接口。这也是 DIP 设计原则的体现(若想了解更多关于设计原则的内容请猛击这里 ^_^)。

 

  • 最重要的第二个问题:可用性如何,也就是说使用起来是否是否方便?

  答:这个问题问得很好,可用性对所有通用组件都是至关重要的,如果太难用还不如自己重头写一个来得方便。因此,ISocketClient 和 IClientSocketListener 接口设计得尽量简单易用(通俗来说就是“傻瓜化”),这两个接口的主要方法均不超过 5 个。

 

  • 最重要的第三个问题:组件的性能如何?

  作为底层的通用组件,性能问题是必须考虑的,绝对不能成为系统的瓶颈。而另一方面,从实际出发,毕竟只是一个客户端组件,它的并发性要求远没有服务端那么高。因此,组件在设计上充分考虑了性能、现实使用情景、可用性和实现复杂性等因素,确保满足性能要求的同时又不会写得太复杂。做出以下两点设计决策:

    1. 在单独线程中实现 Socket 通信交互。这样可以避免与主线程或其他线程相互干扰。
    2. I/O 模型选择 WSAEventSelect。细说一下选择这种 I/O 模型的原因:(各种 I/O 模型的性能比较可以参考:《Windows 网络编程(中文第二版)》第 154 页)
      • 阻塞模型:(不解析,你懂的^_^)
      • 非阻塞模型:(性能太低)
      • WSAAsyncSelect: (两个原因:a、性能太低;b、对于纯 Console 程序还要背负 HWND 实在是伤不起呀!)
      • 重叠 I/O:(有点复杂了)
      • 完成端口:(何必呢?)

 

  唉,理论的东西就先别吹那么多了,直接上代码吧,求你了 !!

  OK!先看看 ISocketClient 和 IClientSocketListener 的接口定义:

复制代码
复制代码
// 组件操作类型
enum EnSocketOperation
{
SO_UNKNOWN = 0,
SO_ACCEPT = 1,
SO_CONNECT = 2,
SO_SEND = 3,
SO_RECEIVE = 4,
};

// 组件监听器基接口
class ISocketListener
{
public:
enum EnHandleResult
{
HR_OK = 0,
HR_IGNORE = 1,
HR_ERROR = 2,
};

public:
  // 已发出数据通知
virtual EnHandleResult OnSend(DWORD dwConnectionID, const BYTE* pData, int iLength) = 0;

  // 已接收数据通知
virtual EnHandleResult OnReceive(DWORD dwConnectionID, const BYTE* pData, int iLength) = 0;

  // 关闭连接通知
virtual EnHandleResult OnClose(DWORD dwConnectionID) = 0;

  // 通信错误通知
virtual EnHandleResult OnError(DWORD dwConnectionID, EnSocketOperation enOperation, int iErrorCode) = 0;


public:
virtual ~ISocketListener() {}
};

// 服务端组件监听器接口(暂时无视之)
class IServerSocketListener : public ISocketListener
{
public:
// 接收连接通知
virtual EnHandleResult OnAccept(DWORD dwConnectionID) = 0;
// 服务关闭通知
virtual EnHandleResult OnServerShutdown() = 0;
};

// 客户端组件监听器接口
class IClientSocketListener : public ISocketListener
{
public:
// 连接完成通知
virtual
EnHandleResult OnConnect(DWORD dwConnectionID) = 0;
};

// 服务端组件接口(暂时无视之)
class ISocketServer
{
public:
enum En_ISS_Error
{
ISS_OK = 0,
ISS_SOCKET_CREATE = 1,
ISS_SOCKET_BIND = 2,
ISS_SOCKET_LISTEN = 3,
ISS_CP_CREATE = 4,
ISS_WORKER_THREAD_CREATE = 5,
ISS_SOCKE_ATTACH_TO_CP = 6,
ISS_ACCEPT_THREAD_CREATE = 7,
};

public:
virtual BOOL Start (LPCTSTR pszBindAddress, USHORT usPort, long lThreadCount) = 0;
virtual BOOL Stop () = 0;
virtual BOOL Send (DWORD dwConnID, const BYTE* pBuffer, int iLen) = 0;
virtual BOOL HasStarted () = 0;
virtual En_ISS_Error GetLastError () = 0;
virtual LPCTSTR GetLastErrorDesc() = 0;
virtual BOOL GetConnectionAddress(DWORD dwConnID, CString& strAddress, USHORT& usPort) = 0;


public:
virtual ~ISocketServer() {}
};

// 服务端组件接口智能指针
typedef auto_ptr<ISocketServer> ISocketServerPtr;

// 客户端组件接口
class ISocketClient
{
public:
// 操作结果码
enum En_ISC_Error
{
ISC_OK = 0,
ISC_CLIENT_HAD_STARTED = 1,
ISC_CLIENT_NOT_STARTED = 2,
ISC_SOCKET_CREATE_FAIL = 3,
ISC_CONNECT_SERVER_FAIL = 4,
ISC_WORKER_CREATE_FAIL = 5,
ISC_NETWORK_ERROR = 6,
ISC_PROTOCOL_ERROR = 7,
};

public:
  // 启动通信
virtual BOOL Start (LPCTSTR pszRemoteAddress, USHORT usPort) = 0;

  // 关闭通信
virtual BOOL Stop () = 0;

  // 发送数据
virtual BOOL Send (DWORD dwConnID, const BYTE* pBuffer, int iLen) = 0;

  // 是否已启动
virtual BOOL HasStarted () = 0;

  // 获取错误码
virtual En_ISC_Error GetLastError () = 0;

  // 获取错误描述
virtual LPCTSTR GetLastErrorDesc() = 0;


public:
virtual ~ISocketClient() {}
};

// 客户端组件接口智能指针
typedef auto_ptr<ISocketClient>
ISocketClientPtr;
复制代码
复制代码

  

  ISocketClient 接口主要有以下三个方法:

  • Start():启动通信
  • Send():发送数据
  • Stop():停止通信

  IClientSocketListener 接口有以下五个通知方法:

  • OnConnect()
  • OnSend()
  • OnReceive()
  • OnClose()
  • OnError() 

   够简单了吧^_^,使用者只需通过三个方法操作组件,然后处理五个组件通知。下面我们再看看组件的具体实现,先看组件类定义:

复制代码
复制代码
/* 组件实现类 */
class CSocketClient : public ISocketClient
{
// ISocketClient 接口方法
public:
  virtual BOOL Start (LPCTSTR pszRemoteAddress, USHORT usPortt);
  virtual BOOL Stop ();
  virtual BOOL Send (DWORD dwConnID, const BYTE* pBuffer, int iLen);
  virtual BOOL HasStarted () {return m_bStarted;}
  virtual En_ISC_Error GetLastError () {return sm_enLastError;}
  virtual LPCTSTR GetLastErrorDesc();

private:
BOOL CreateClientSocket();
BOOL ConnectToServer(LPCTSTR pszRemoteAddress, USHORT usPort);
BOOL CreateWorkerThread();
// 网络事件处理方法
BOOL ProcessNetworkEvent();
void WaitForWorkerThreadEnd();
BOOL ReadData();
BOOL SendData();

void SetLastError(En_ISC_Error code, LPCTSTR func, int ec);

// 通信线程函数
static
#ifndef _WIN32_WCE
UINT
#else
DWORD
#endif
WINAPI WorkerThreadProc(LPVOID pv);

private:
static const int RECEIVE_BUFFER_SIZE = 8 * 1024;
static const int WORKER_THREAD_END_TIME = 3 * 1000;

static const long DEFALUT_KEEPALIVE_TIMES = 3;
static const long DEFALUT_KEEPALIVE_INTERVAL = 10 * 1000;


// 构造函数
public:

  CSocketClient(IClientSocketListener* pListener)
  : m_pListener(pListener) // 设置监听器对象
, m_soClient(INVALID_SOCKET)

  , m_evSocket(NULL)
  , m_dwConnID(0)
  , m_hWorker(NULL)
  , m_dwWorkerID(0)
  , m_bStarted(FALSE)
#ifdef _WIN32_WCE
  , sm_enLastError(ISC_OK)
#endif
  {
    ASSERT(m_pListener);
  }

virtual ~CSocketClient() {if(HasStarted()) Stop();}

private:
// 这是神马 ???
CInitSocket m_wsSocket;


SOCKET m_soClient;
HANDLE m_evSocket;
DWORD m_dwConnID;

CCriSec m_scStop;
CEvt m_evStop;
HANDLE m_hWorker;

#ifndef _WIN32_WCE
UINT
#else
DWORD
#endif
m_dwWorkerID;

CBufferPtr m_sndBuffer;
CCriSec m_scBuffer;
CEvt m_evBuffer;

volatile BOOL m_bStarted;

private:
// 监听器对象指针
  IClientSocketListener* m_pListener;

#ifndef _WIN32_WCE
__declspec(thread) static En_ISC_Error sm_enLastError;
#else
volatile En_ISC_Error sm_enLastError;
#endif
};
复制代码
复制代码

 

  从上面的定义可以看出,组件实现类本身并没有提供额外的公共方法,它完全是可以被替换的。组件在构造函数中接收监听器对象,并且保存为其成员属性,因此可以在需要的时候向监听器发送事件通知。

  另外,不知各位看官是否注意到一个奇怪的成员属性:“CInitSocket m_wsSocket; ”,这个属性在其它地方从来都不会用到,那么它是干嘛的呢?在回答这个问题之前,首先想问问大家:Windows Socket 操作的整个操作过程中,第一个以及最后一个被调用的方法是什么?是 socket()、connect()、bind()、还是 closesocket() 吗?都错!答案是 —— ::WSAStartup() 和 ::WSACleanup()。每个程序都要调用一下这两个方法确实是很烦的,又不雅观。 其实,m_wsSocket 的唯一目的就是为了避免手工调用者两个方法,看看它的定义就明白了:

复制代码
复制代码
class CInitSocket
{
public:
CInitSocket(LPWSADATA lpWSAData = NULL, BYTE minorVersion = 2, BYTE majorVersion = 2)
{
LPWSADATA lpTemp = lpWSAData;
if(!lpTemp)
lpTemp = (LPWSADATA)_alloca(sizeof(WSADATA));

m_iResult = ::WSAStartup(MAKEWORD(minorVersion, majorVersion), lpTemp);
}

~CInitSocket()
{
if(IsValid())
::WSACleanup();
}

int GetResult() {return m_iResult;}
BOOL IsValid() {return m_iResult == 0;}

private:
int m_iResult;
};
复制代码
复制代码

 

   现在我们看看组件类实现文件中几个重要方法的定义:

复制代码
复制代码
// 组件事件触发宏定义
#define FireConnect(id) m_pListener->OnConnect(id)
#define FireSend(id, data, len) (m_bStarted ? m_pListener->OnSend(id, data, len) : ISocketListener::HR_IGNORE)
#define FireReceive(id, data, len) (m_bStarted ? m_pListener->OnReceive(id, data, len) : ISocketListener::HR_IGNORE)
#define FireClose(id) (m_bStarted ? m_pListener->OnClose(id) : ISocketListener::HR_IGNORE)
#define FireError(id, op, code) (m_bStarted ? m_pListener->OnError(id, op, code) : ISocketListener::HR_IGNORE)

// 启动组件
BOOL CSocketClient::Start(LPCTSTR pszRemoteAddress, USHORT usPort)
{
BOOL isOK = FALSE;

if(HasStarted())
{
SetLastError(ISC_CLIENT_HAD_STARTED, _T(__FUNCTION__), 0);
return isOK;
}

// 创建 socket
if(CreateClientSocket())
{
// 连接服务器(内部会调用 FireConnect()
if(ConnectToServer(pszRemoteAddress, usPort))
{
// 创建工作线程
if(CreateWorkerThread())
isOK = TRUE;
else
SetLastError(ISC_WORKER_CREATE_FAIL, _T(__FUNCTION__), 0);
}
else
SetLastError(ISC_CONNECT_SERVER_FAIL, _T(__FUNCTION__), ::WSAGetLastError());
}
else
SetLastError(ISC_SOCKET_CREATE_FAIL, _T(__FUNCTION__), ::WSAGetLastError());

isOK ? m_bStarted = TRUE : Stop();

return isOK;
}

// 关闭组件
BOOL CSocketClient::Stop()
{
{
CCriSecLock locallock(m_scStop);

m_bStarted = FALSE;

if(m_hWorker != NULL)
{
// 停止工作线程
if(::GetCurrentThreadId() != m_dwWorkerID)
WaitForWorkerThreadEnd();

::CloseHandle(m_hWorker);
m_hWorker = NULL;
m_dwWorkerID = 0;
}

if(m_evSocket != NULL)
{
// 关闭 WSAEvent
::WSACloseEvent(m_evSocket);
m_evSocket = NULL;
}

if(m_soClient != INVALID_SOCKET)
{
// 关闭socket
shutdown(m_soClient, SD_SEND);
closesocket(m_soClient);
m_soClient = INVALID_SOCKET;
}

m_dwConnID = 0;
}

// 释放其它资源
m_sndBuffer.Free();
m_evBuffer.Reset();
m_evStop.Reset();

return TRUE;
}

// 发送数据
BOOL CSocketClient::Send(DWORD dwConnID, const BYTE* pBuffer, int iLen)
{
ASSERT(iLen > 0);

if(!HasStarted())
{
SetLastError(ISC_CLIENT_NOT_STARTED, _T(__FUNCTION__), 0);
return FALSE;
}

CCriSecLock locallock(m_scBuffer);

// 把数据存入缓冲器
m_sndBuffer.Cat(pBuffer, iLen);
// 唤醒工作现场,发送数据
m_evBuffer.Set();

return TRUE;
}

// 工作线程函数
#ifndef _WIN32_WCE
UINT
#else
DWORD
#endif
WINAPI CSocketClient::WorkerThreadProc(LPVOID pv)
{
CSocketClient* pClient = (CSocketClient*)pv;

TRACE0("---------------> 启动工作线程 <---------------\n");

HANDLE hEvents[] = {pClient->m_evSocket, pClient->m_evBuffer, pClient->m_evStop};

while(pClient->HasStarted())
{
// 等待 socket 事件、发送数据事件和停止通信事件
DWORD retval = ::MsgWaitForMultipleObjectsEx(3, hEvents, WSA_INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE);

if(retval == WSA_WAIT_EVENT_0)
{
// 处理网络消息
if(!pClient->ProcessNetworkEvent())
{
if(pClient->HasStarted())
pClient->Stop();

break;
}
}
else if(retval == WSA_WAIT_EVENT_0 + 1)
{
// 发送数据(内部调用 FireSend()
if(!pClient->SendData())
{
if(pClient->HasStarted())
pClient->Stop();

break;
}
}
else if(retval == WSA_WAIT_EVENT_0 + 2)
break;
else if(retval == WSA_WAIT_EVENT_0 + 3)
// 消息循环
::PeekMessageLoop();
else
ASSERT(FALSE);
}

TRACE0("---------------> 退出工作线程 <---------------\n");

return 0;
}

// 处理网络消息
BOOL CSocketClient::ProcessNetworkEvent()
{
::WSAResetEvent(m_evSocket);

WSANETWORKEVENTS events;

int rc = ::WSAEnumNetworkEvents(m_soClient, m_evSocket, &events);

if(rc == SOCKET_ERROR)
{
int code = ::WSAGetLastError();
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), code);
FireError(m_dwConnID, SO_UNKNOWN, code);

return FALSE;
}

/* 可读取 */
if(events.lNetworkEvents & FD_READ)
{
int iCode = events.iErrorCode[FD_READ_BIT];

if(iCode == 0)
// 读取数据(内部调用 FireReceive()
return ReadData();
else
{
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), iCode);
      FireError(m_dwConnID, SO_RECEIVE, iCode);
return FALSE;
}
}

/* 可发送 */
if(events.lNetworkEvents & FD_WRITE)
{
int iCode = events.iErrorCode[FD_WRITE_BIT];

if(iCode == 0)
// 发送数据(内部调用 FireSend()
return SendData();
else
{
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), iCode);
        FireError(m_dwConnID, SO_SEND, iCode);
return FALSE;
}
}

/* socket 已关闭 */
if(events.lNetworkEvents & FD_CLOSE)
{
int iCode = events.iErrorCode[FD_CLOSE_BIT];

if(iCode == 0)
      FireClose(m_dwConnID);
else
{
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), iCode);
FireError(m_dwConnID, SO_UNKNOWN, iCode);
}

return FALSE;
}

return TRUE;
}
复制代码
复制代码

  

  从上面的代码可以看出:通信过程中,组件的使用者不需要对通信过程进行任何干预,整个底层通信过程对使用者来说是透明的,使用只需集中精力处理好几个组件通知。下面来看看组件的一个使用示例:

复制代码
复制代码
/* 组件使用者:实现 IClientSocketListener */
class CMainClient : public IClientSocketListener
{
// 这些方法会操作组件
public:
bool Login(LPCTSTR pszAddress, USHORT usPort, const T_101_Data* pData);
bool Logout(const T_201_Data* pData);
BOOL SendData(EnCommandType enCmdType, const TCommandData* pCmdData, WORD wCmdDataLen);
long GetLastError();
LPCTSTR GetLastErrorDesc();

// 实现 IClientSocketListener
public:
  virtual EnHandleResult OnConnect(DWORD dwConnectionID);
  virtual EnHandleResult OnSend(DWORD dwConnectionID, const BYTE* pData, int iLength);
  virtual EnHandleResult OnReceive(DWORD dwConnectionID, const BYTE* pData, int iLen);
  virtual EnHandleResult OnClose(DWORD dwConnectionID);
  virtual EnHandleResult OnError(DWORD dwConnectionID, EnSocketOperation enOperation, int iErrorCode);

private:
BOOL ParseReceiveBuffer();

// 其它方法 。。。

// 构造函数
public:
  CMainClient()
  // 创建组件,并把自己设置为组件的监听器
: m_pscClient(new CSocketClient(this))

  , m_dwConnID(0)
  {
  }

virtual ~CMainClient() {}

private:
// 组件属性
  ISocketClientPtr m_pscClient;
DWORD m_dwConnID;

// 其它属性 。。。
};
复制代码
复制代码

 

复制代码
复制代码
BOOL CMainClient::Login(LPCTSTR pszAddress, USHORT usPort, const T_101_Data* pData)
{
// 启动通信
return m_pscClient->Start(pszAddress, usPort) &&
SendData(CS_C_LOGIN_REQ, pData, sizeof(T_101_Data));
}

BOOL CMainClient::Logout(const T_201_Data* pData)
{
if(pData)
{
SendData(CS_C_SET_STATUS, pData, sizeof(T_201_Data));
::WaitWithMessageLoop(LOGOUT_WAIT_TIME);
}

// 停止通信
return m_pscClient->Stop();
}

BOOL CMainClient::SendData(EnCommandType enCmdType, const TCommandData* pCmdData, WORD wCmdDataLen)
{
const WORD wBufferLen = CMD_ADDITIVE_SIZE + wCmdDataLen;
CPrivateHeapByteBuffer buffer(m_hpPrivate, wBufferLen);
BYTE* pBuffer = buffer;

memcpy(pBuffer, &wBufferLen, CMD_LEN_SIZE);
pBuffer += CMD_LEN_SIZE;
memcpy(pBuffer, &enCmdType, CMD_TYPE_SIZE);
pBuffer += CMD_TYPE_SIZE;
memcpy(pBuffer, pCmdData, wCmdDataLen);
pBuffer += wCmdDataLen;
memcpy(pBuffer, &CMD_FLAG, CMD_FLAG_SIZE);

// 发送数据
return m_pscClient->Send(m_dwConnID, buffer, wBufferLen);
}

long CMainClient::GetLastError()
{
// 获取通信错误码
return m_pscClient->GetLastError();
}

LPCTSTR CMainClient::GetLastErrorDesc()
{
// 获取通信错误描述
return m_pscClient->GetLastErrorDesc();
}

/* 处理连接成功事件 */
ISocketListener::EnHandleResult CMainClient::OnConnect(DWORD dwConnectionID)
{
TRACE1("<CNNID: %d> 已连接\n", dwConnectionID);
m_dwConnID = dwConnectionID;
return HR_OK;
}

/* 处理数据已发出事件 */
ISocketListener::EnHandleResult CMainClient::OnSend(DWORD dwConnectionID, const BYTE* pData, int iLength)
{
TRACE2("<CNNID: %d> 发出数据包 (%d bytes)\n", dwConnectionID, iLength);
return HR_OK;
}

/* 处理接收到数据事件*/
ISocketListener::EnHandleResult CMainClient::OnReceive(DWORD dwConnectionID, const BYTE* pData, int iLen)
{
TRACE2("<CNNID: %d> 接收数据包 (%d bytes)\n", dwConnectionID, iLen);

ASSERT(pData != NULL && iLen > 0);

// 保存数据
m_rcBuffer.Cat(pData, iLen);

// 解析数据
return ParseReceiveBuffer() ? HR_OK : HR_ERROR;;
}

/* 处理通信关闭事件*/
ISocketListener::EnHandleResult CMainClient::OnClose(DWORD dwConnectionID)
{
TRACE1("CNNID: %d> 关闭连接\n", dwConnectionID);

// 清理缓冲区
m_rcBuffer.Realloc(0);
return HR_OK;
}

/* 处理通信错误事件 */
ISocketListener::EnHandleResult CMainClient::OnError(DWORD dwConnectionID, EnSocketOperation enOperation, int iErrorCode)
{
TRACE3("<CNNID: %d> 网络错误 (OP: %d, CO: %d)\n", dwConnectionID, enOperation, iErrorCode);

// 清理缓冲区
m_rcBuffer.Realloc(0);

return HR_OK;
}
复制代码
复制代码

 

  好了,码了一个晚上的字,累啊!到此为止吧,感谢收看~

  敬请继续收看《基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现》,晚安 ^_^

 

  (想看源代码的朋友请狂点这里

目录
相关文章
|
2月前
|
NoSQL Redis 数据安全/隐私保护
Redis 最流行的图形化界面下载及使用超详细教程(带安装包)! redis windows客户端下载
文章提供了Redis最流行的图形化界面工具Another Redis Desktop Manager的下载及使用教程,包括如何下载、解压、连接Redis服务器以及使用控制台和查看数据类型详细信息。
162 6
Redis 最流行的图形化界面下载及使用超详细教程(带安装包)! redis windows客户端下载
|
2月前
|
NoSQL Redis 数据库
Redis 图形化界面下载及使用超详细教程(带安装包)! redis windows下客户端下载
文章提供了Redis图形化界面工具的下载及使用教程,包括如何连接本地Redis服务器、操作键值对、查看日志和使用命令行等功能。
159 0
Redis 图形化界面下载及使用超详细教程(带安装包)! redis windows下客户端下载
|
2月前
|
API 开发工具 C#
神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?
本文将介绍,ClkLog针对神策不支持全埋点的客户端实现用户访问基础统计分析 1。
神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?
|
2月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
152 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
2月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
169 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
2月前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
147 1
|
3月前
|
Windows
Windows操作系统部署安装Kerberos客户端
详细介绍了在Windows操作系统上部署安装Kerberos客户端的完整过程,包括下载安装包、安装步骤、自定义安装路径、修改环境变量、配置hosts文件和Kerberos配置文件,以及安装后的验证步骤。
411 3
Windows操作系统部署安装Kerberos客户端
|
4月前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
3月前
|
网络协议 Linux
TCP 和 UDP 的 Socket 调用
【9月更文挑战第6天】
|
4月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
140 0