C++socket编程.docx
《C++socket编程.docx》由会员分享,可在线阅读,更多相关《C++socket编程.docx(75页珍藏版)》请在冰点文库上搜索。
C++socket编程
第3章网络应用
3.1网络编程基础
多媒体技术与网络技术的结合,使得网络生活变得多姿多彩。
从此,网络生活很迷人;网络改变了和改变着人们原本的生活方式。
姑且认为DirectShow是单机的多媒体技术,一旦融合了网络技术,DirectShow更显现了它强大的生命力。
本章将着重介绍DirectShow技术在网络方面的应用。
网络编程,当然要用到WindowsSocket(套接字)技术。
Socket相关的操作由一系列API函数来完成,比如socket、bind、listen、connect、accept、send、sendto、recv、recvfrom等。
调用这些API函数有一定的先后次序,有些函数的参数还比较复杂,对于开发者来说,不是很好用。
于是,微软的MFC提供了两个类:
CAsyncSocket和CSocket,极大地方便了Socket功能的使用。
这两个类的继承关系如图3.1。
图3.1MFCSocket类的继承关系
CAsyncSocket类在较低层次上封装了WindowsSocketAPI,并且通过内建一个(隐藏的)窗口,实现了适合Windows应用的异步机制(WindowsSocketAPI默认情况下工作在阻塞模式,不方便直接在消息驱动的Windows程序上使用)。
CSocket类从CAsyncSocket类派生,进一步简化了Socket功能的应用。
不过很遗憾,正因为这两个类都内建了一个窗口,它们并不是线程安全的(thread-safe);如果要在多线程环境下应用Socket功能,建议自行封装SocketAPI函数。
使用Socket传输数据主要有两种方式:
TCP传输和UDP传输。
(OSI参考模型将网络通信分成7个层次,从低往上依次为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层;TCP和UDP均是传输层的协议。
)下面,就分别来介绍这两种数据传输方式。
提示:
本章在介绍网络通信双方的时候,会使用两组关键词:
服务器-客户机和本地端-远程端。
其中,服务器-客户机是根据角色来界定的;而本地端-远程端是一个相对概念,依据不同的参照物,可以分别表示不同的角色。
比如以服务器为参照物,可以称服务器为本地端,称客户机为远程端;而如果以客户机为参照物,可以称客户机为本地端,称服务器为远程端。
3.1.1TCP传输
TCP,TransferControlProtocol的缩写(传输控制协议),是一种面向连接的网络传输协议。
TCP协议的特点是,支持多数据流操作,提供流控和错误控制,甚至能完成对乱序到达报文的重新排序等。
因此,TCP提供了可靠的应用数据传输服务。
通信双方使用TCP传输的一般过程参考如图3.2。
图3.2TCP通信的一般过程
本节将要实现一个TCP传输的演示程序TCPDemo,它包括服务器和客户机两个部分。
它们的程序界面如图3.3。
图3.3TCP传输演示程序界面
TCPDemo的演示过程如下:
(1)将服务器和客户机两部分程序都运行起来(此时服务器已经启动了侦听客户机连接请求的子线程,侦听端口号为10028)。
(2)在客户机程序界面上输入服务器的IP地址(如果服务器和客户机运行在同一台机器上,IP地址可以指定为127.0.0.1)、侦听端口号(因为服务器在10028端口上侦听,这里也应该指定为10028)。
(3)点击客户机程序界面上的“Connect”按钮,向服务器发送Socket连接请求。
(4)服务器侦听到有客户机的连接请求后便接受它(于是在两个程序之间就建立了一条可靠的Socket连接)。
然后服务器会向客户机发送两次字符串数据。
(5)客户机接收到数据后,弹出两次如图3.4的消息框。
图3.4TCP传输客户机接收到数据后显示的消息框
提示:
TCPDemo为什么使用10028作为TCP通信的端口号?
因为TCP数据包的TCP头结构中,使用了16位的域来表示一个端口号。
因此,有65536个可能的端口号。
不过,0-1023是周知口(众所周知的端口,比如80是超文本传输协议http的端口,25是简单邮件传输协议smtp的端口,20和21是文件传输协议ftp的端口等),比1023大的端口号通常被称为高端口号。
应用程序一般使用高端口号提供自己的通信服务。
TCPDemo使用10028端口是偶然的,只要比1023大就可以了。
TCPDemo在具体实现时,设计了一个CTCPListener类专门用于服务器对特定TCP端口的侦听。
另外,设计了一个CStreamSocket类专门用于TCP数据的传输。
CStreamSocket作为基类,服务器程序从它派生出另一个类CSocketSender专门用于数据的发送,客户机程序从它派生出CSocketReceiver类专门用于数据的接收。
这些类的继承结构如图3.5。
图3.5TCPDemo的类继承结构
提示:
关于CMsgStation和CMsgReceiver两个类的功能介绍,请读者另行参考本书的“2.4.1一种不错的设计模式”。
//
//CTCPListener.h
//
#ifndef__H_CTCPListener__
#define__H_CTCPListener__
#include"CMsgStation.h"
classCTCPListener:
publicCMsgStation
{
protected:
SOCKETmListener;//用于侦听的Socket
SOCKETmAccepted;//用于与远程端建立连接的Socket
WORDmListenPort;//侦听端口号
BOOLmIsListening;//是否正在侦听的标记
HANDLEmLsnThread;//侦听线程
public:
CTCPListener();
virtual~CTCPListener();
public:
//设置/得到侦听的端口号
voidSetListenPort(WORDinPort);
WORDGetListenPort(void);
//创建/销毁用于侦听的Socket
BOOLCreate(void);
voidDeleteListener(void);
//销毁服务器与客户机建立连接的Socket
voidDeleteAccepted(void);
//启动/停止侦听线程
BOOLStartListening(void);
voidStopListening(void);
//得到服务器与客户机建立连接的Socket(用于数据传输)
SOCKETGetAccepted(void);
private:
BOOLAccept(void);//接受远程端的连接请求
staticDWORDWINAPIListeningThrd(void*pParam);//侦听线程执行体
};
#endif//__H_CTCPListener__
//
//CTCPListener.cpp
//
#include"stdafx.h"
#include"CTCPListener.h"
#include"Netdefs.h"
#ifdef_DEBUG
#definenewDEBUG_NEW
#undefTHIS_FILE
staticcharTHIS_FILE[]=__FILE__;
#endif
//////////////////////////////////////////////////////////////////////////////
CTCPListener:
:
CTCPListener()
{
//参数初始化
mListener=INVALID_SOCKET;
mAccepted=INVALID_SOCKET;
//默认在10028端口上侦听
mListenPort=10028;
mLsnThread=NULL;
mIsListening=FALSE;
}
CTCPListener:
:
~CTCPListener()
{
//销毁Socket
DeleteAccepted();
DeleteListener();
//停止侦听线程
StopListening();
}
//设置侦听端口号
voidCTCPListener:
:
SetListenPort(WORDinPort)
{
mListenPort=inPort;
}
//得到侦听端口号
WORDCTCPListener:
:
GetListenPort(void)
{
returnmListenPort;
}
//创建用于侦听的Socket
BOOLCTCPListener:
:
Create(void)
{
DeleteListener();//销毁侦听Socket
intval=0;
BOOLpass=FALSE;
//创建一个TCP传输的Socket
mListener=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
if(mListener!
=INVALID_SOCKET)
{
//在Socket上进行参数设置
BOOLsopt=TRUE;
setsockopt(mListener,IPPROTO_TCP,TCP_NODELAY,
(char*)&sopt,sizeof(BOOL));
//在销毁Socket时不必等待未发送完的数据完全发送出去
setsockopt(mListener,SOL_SOCKET,SO_DONTLINGER,
(char*)&sopt,sizeof(BOOL));
//绑定Socket到指定的侦听端口
SOCKADDR_INaddr;
memset(&addr,0,sizeof(SOCKADDR_IN));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port=htons(mListenPort);
val=bind(mListener,(structsockaddr*)&addr,sizeof(addr));
pass=(val!
=SOCKET_ERROR);
}
if(pass)
{
//将Socket置于侦听状态
val=listen(mListener,SOMAXCONN);
pass=(val!
=SOCKET_ERROR);
}
if(!
pass)
{
DeleteListener();
}
returnpass;
}
//销毁用于侦听的Socket
voidCTCPListener:
:
DeleteListener(void)
{
if(mListener!
=INVALID_SOCKET)
{
closesocket(mListener);
mListener=INVALID_SOCKET;
}
}
//销毁服务器与客户机建立连接的Socket
voidCTCPListener:
:
DeleteAccepted(void)
{
if(mAccepted!
=INVALID_SOCKET)
{
closesocket(mAccepted);
mAccepted=INVALID_SOCKET;
}
}
//启动侦听线程(因为用于接受连接请求的accept函数调用时会阻塞)
BOOLCTCPListener:
:
StartListening(void)
{
//如果侦听Socket没有创建,则创建它
if(mListener==INVALID_SOCKET)
{
Create();
}
if(mListener!
=INVALID_SOCKET)
{
if(mIsListening)
{
returnTRUE;
}
//启动侦听线程
DWORDthreadID=0;
mLsnThread=CreateThread(NULL,0,ListeningThrd,
this,0,&threadID);
return(mLsnThread!
=NULL);
}
returnFALSE;
}
//停止侦听线程
voidCTCPListener:
:
StopListening(void)
{
if(mListener!
=INVALID_SOCKET&&mIsListening)
{
//销毁侦听Socket,于是accept将脱离阻塞状态
DeleteListener();
//等待侦听线程完全退出
if(mLsnThread!
=NULL)
{
WaitForSingleObject(mLsnThread,INFINITE);
mLsnThread=NULL;
}
}
}
//接受远程端的连接请求(创建一个新的Socket用于与远程端建立一条连接)
BOOLCTCPListener:
:
Accept(void)
{
if(mListener!
=INVALID_SOCKET)
{
SOCKADDR_INsaddr;
intlen=sizeof(SOCKADDR_IN);
//侦听远程端的连接请求(如果没有连接请求,这个函数将阻塞)
SOCKETaccepted=accept(mListener,(SOCKADDR*)&saddr,&len);
if(accepted==INVALID_SOCKET)
{
returnFALSE;
}
//注意:
目前仅支持建立一条Socket连接!
//在建立新的连接之前将以前的连接断开
DeleteAccepted();
//保存与远程端建立连接的Socket
mAccepted=accepted;
//在Socket上设置一些参数
BOOLsopt=TRUE;
setsockopt(mAccepted,IPPROTO_TCP,TCP_NODELAY,
(char*)&sopt,sizeof(BOOL));
setsockopt(mAccepted,SOL_SOCKET,SO_DONTLINGER,
(char*)&sopt,sizeof(BOOL));
returnTRUE;
}
returnFALSE;
}
//当与远程端连接的Socket取出之后,保存该Socket的变量置为无效
//取出的Socket由取出者负责销毁
SOCKETCTCPListener:
:
GetAccepted(void)
{
SOCKETret=mAccepted;
mAccepted=INVALID_SOCKET;
returnret;
}
//侦听线程的函数执行体
DWORDWINAPICTCPListener:
:
ListeningThrd(void*pParam)
{
ASSERT(pParam);
//获得侦听对象指针
CTCPListener*pListen=(CTCPListener*)pParam;
pListen->mIsListening=TRUE;
while(pListen->mIsListening)
{
//开始侦听(如果没有远程端发送连接请求,这个函数将阻塞)
if(!
pListen->Accept())
{
pListen->mIsListening=FALSE;
break;
}
else
{
//constlongcNewSocketAccepted=6688;
//发送给上层观察者一个自定义消息cNewSocketAccepted,
//表示一条Socket连接已经建立(可以用它进行数据传输了!
)
pListen->Broadcast(cNewSocketAccepted);
}
}
return1;
}
//
//CStreamSocket.h
//
#ifndef__H_CStreamSocket__
#define__H_CStreamSocket__
classCStreamSocket
{
protected:
SOCKETmSocket;//用于数据发送或接收的Socket
BOOLmIsConnected;//Socket是否已经建立了连接的标记
BOOLmIsReceiving;//使用独立的线程进行数据接收
HANDLEmRcvThread;
BOOLmIsSending;//使用独立的线程进行数据发送
HANDLEmSndThread;
public:
CStreamSocket();
virtual~CStreamSocket();
public:
BOOLAttach(SOCKETinSocket);//关联一个Socket
voidDetach(void);//销毁Socket
//向指定IP地址、端口号的机器发送连接请求
BOOLConnectTo(constchar*inTarget,WORDinPort);
BOOLIsConnected(void){returnmIsConnected;};
//用于数据接收的控制函数
BOOLStartReceiving(void);
voidStopReceiving(void);
BOOLIsReceiving(void){returnmIsReceiving;};
//用于数据发送的控制函数
BOOLStartSending(void);
voidStopSending(void);
BOOLIsSending(void){returnmIsSending;};
protected:
staticDWORDWINAPIReceivingThrd(void*pParam);//接收线程执行体
staticDWORDWINAPISendingThrd(void*pParam);//发送线程执行体
//接收/发送数据循环过程(虚函数,供子类定制)
virtualvoidReceivingLoop(void);
virtualvoidSendingLoop(void);
};
#endif//__H_CStreamSocket__
//
//CStreamSocket.cpp
//
#include"stdafx.h"
#include"CStreamSocket.h"
#include"UNetwork.h"
#ifdef_DEBUG
#definenewDEBUG_NEW
#undefTHIS_FILE
staticcharTHIS_FILE[]=__FILE__;
#endif
//////////////////////////////////////////////////////////////////////////////
CStreamSocket:
:
CStreamSocket()
{
//参数初始化
mSocket=INVALID_SOCKET;
mIsConnected=FALSE;
mIsReceiving=FALSE;
mIsSending=FALSE;
mRcvThread=NULL;
mSndThread=NULL;
}
//销毁Socket,停止发送/接收线程
CStreamSocket:
:
~CStreamSocket()
{
Detach();
StopSending();
StopReceiving();
}
//关联一个Socket到本包装对象
BOOLCStreamSocket:
:
Attach(SOCKETinSocket)
{
//如果已经包装了一个Socket,则返回一个错误值
if(mSocket!
=INVALID_SOCKET)
{
returnFALSE;
}
//保存Socket句柄
mSocket=inSocket;
mIsConnected=TRUE;
returnTRUE;
}
//销毁Socket
voidCStreamSocket:
:
Detach(void)
{
if(mSocket!
=INVALID_SOCKET)
{
closesocket(mSocket);
mSocket=INVALID_SOCKET;
mIsConnected=FALSE;
}
}
//向指定IP地址、端口号的机器发送连接请求
BOOLCStreamSocket:
:
ConnectTo(constchar*inTarget,WORDinPort)
{
if(mIsConnected)
{
returnTRUE;
}
//首先创建一个TCP传输的Socket
mSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(mSocket!
=INVALID_SOCKET)
{
//在成功创建的Socket上调整参数
BOOLsopt=TRUE;
setsockopt(mSocket,IPPROTO_TCP,TCP_NODELAY,
(char*)&sopt,sizeof(BOOL));
setsockopt(mSocket,SOL_SOCKET,SO_DONTLINGER,
(char*)&sopt,sizeof(BOOL));
//向服务器发送连接请求
SOCKADDR_INsaddr;
memset(&saddr,0,sizeof(SOCKADDR_IN));
saddr.sin_addr.S_un.S_addr=inet_addr(inTarget);
saddr.sin_family=AF_INET;
saddr.sin