| 学习C++已经有一段时间了,一直都是学习基础的东西,每次写的代码都比较少,没有明确的学习目标,基础还是基础,漫无边际的,基本上都是做一道或者几道算法题,连一个小小的实战都没有,也不知道自己学得怎么样了,现在终于有一个小小的实战了《C++ 
                          一个网络编程实例》。由于自己一直在做C#,只能业余时间学习C++,都说C++ 是那么的难,暂时还没有感觉到有多难,毕竟写代码也有两年多了。我要学习多久才能进一家做C++研发的公司呢? 相信在不远处有一家C++研发公司在等着我。 这只是一个小小的实例,包括Socket编程、多线程、文件操作。 简单介绍:他实现了点对点聊天,一个服务器,一个客户端,主线程用来发送数据,启动一个子线程用来接收数据,服务器记录聊天内容。他只是用上了上面所说的三个技术,如果你对上面三个技术不是很熟,或许对你有点帮助,如果你很熟,既然来了希望你能指导一下我,如果你是高手希望你能指导一下我的编码问题。我太渴望写出高效简洁的代码。 废话就少说了,程序里处处都是注释,你可以选择看看我的代码,或是选择直接运行看看,《源码下载》。 服务器代码: 
                           
                            | // Server.cpp : 定义控制台应用程序的入口点。
 #include "stdafx.h"
 #include <windows.h>
 #include <process.h>
 #include <iostream>
 #include "FileLog.h"
 #include "time.h"
 usingnamespace std;
 #pragma comment(lib,"ws2_32.lib")
 //多线程调用的方法只有一个指针型的参数,有时候需要多个参数,所以定义一个结构,参数作为结构的字段typedef struct _receiveStruct
 {
 SOCKET *Socket;
 FileLog *fileLog;
 _receiveStruct(SOCKET *_socket,FileLog *_fileLog):Socket(_socket),fileLog(_fileLog){}
 } ReceiveStruct;
 //获取今天日期的字符串string GetDate(constchar*format)
 {
 time_t tm;
 struct tm *now;
 char timebuf[20];
 time(&tm);
 now=localtime(&tm);
 strftime(timebuf,sizeof(timebuf)/sizeof(char),format,now);
 returnstring(timebuf);
 }
 //接收数据线程void receive(PVOID param)
 {
 ReceiveStruct* receiveStruct=(ReceiveStruct*)param;
 char buf[2048];
 int bytes;
 while(1)
 {
 //接收数据
 if((bytes=recv(*receiveStruct->Socket,buf,sizeof(buf),0))==SOCKET_ERROR){
 cout<<"接收数据失败!\n";
 _endthread();//终止当前线程
 }
 buf[bytes]='\0';
 cout<<"客户端说:"<<buf<<endl;
 receiveStruct->fileLog->Write("客户端 
                                ").WriteLine(GetDate("%Y-%m-%d %H:%M:%S").c_str()).WriteLine(buf);//记录聊天内容
 }
 }
 //获取本机IP
 in_addr getHostName(void)
 {
 char host_name[255];
 //获取本地主机名称
 if (gethostname(host_name, sizeof(host_name)) 
                                == SOCKET_ERROR) {
 cout<<"Error %d when getting local 
                                host name."<<WSAGetLastError();
 Sleep(3000);
 exit(-1);
 }
 
 //从主机名数据库中得到对应的“IP”
 struct hostent *phe = gethostbyname(host_name);
 if (phe ==0) {
 cout<<"Yow! Bad host lookup.";
 Sleep(3000);
 exit(-1);
 }
  struct in_addr addr;memcpy(&addr, phe->h_addr_list[0], sizeof(struct 
                                in_addr));
 return addr;
 }
 //启动服务器
 SOCKET StartServer(void)
 {
 //创建套接字
 SOCKET serverSocket;
 if((serverSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){
 cout<<"创建套接字失败!";
 Sleep(3000);
 exit(-1);
 }
 short port=1986;
 struct sockaddr_in serverAddress;
 //初始化指定的内存区域
 memset(&serverAddress,0,sizeof(sockaddr_in));
 serverAddress.sin_family=AF_INET;
 serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 serverAddress.sin_port = htons(port);
  //绑定if(bind(serverSocket,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){
 cout<<"套接字绑定到端口失败!端口:"<<port;
 Sleep(3000);
 exit(-1);
 }
  //进入侦听状态if(listen(serverSocket,SOMAXCONN)==SOCKET_ERROR){
 cout<<"侦听失败!";
 Sleep(3000);
 exit(-1);
 }
 
 //获取服务器IP
 struct in_addr addr = getHostName();
 cout<<"Server "<<inet_ntoa(addr)<<" 
                                : "<<port<<" is listening......"<<endl;
 return serverSocket;
 }
 //接收客户端连接
 SOCKET ReceiveConnect(SOCKET &serverSocket)
 {
 SOCKET clientSocket;//用来和客户端通信的套接字
 struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址
 memset(&clientAddress,0,sizeof(clientAddress));//初始化存放客户端信息的内存
 int addrlen =sizeof(clientAddress);
 
 //接受连接
 if((clientSocket=accept(serverSocket,(sockaddr*)&clientAddress,&addrlen))==INVALID_SOCKET){
 cout<<"接受客户端连接失败!";
 Sleep(3000);
 exit(-1);
 }
 cout<<"Accept connection from "<<inet_ntoa(clientAddress.sin_addr)<<endl;
 return clientSocket;
 }
 //发送数据
 void SendMsg(SOCKET &clientSocket,FileLog 
                                &fileLog)
 {
 char buf[2048];
 while(1){
 cout<<"服务器说:";
 gets_s(buf);
 if(send(clientSocket,buf,strlen(buf),0)==SOCKET_ERROR){
 cout<<"发送数据失败!"<<endl;
 Sleep(3000);
 exit(-1);
 }
 fileLog.Write("服务器 ").WriteLine(GetDate("%Y-%m-%d 
                                %H:%M:%S").c_str()).WriteLine(buf);//记录聊天内容
 }
 }
 
 int main(int argc, char* argv[]){WSADATA wsa;//WSADATA结构被用来保存函数WSAStartup返回的Windows 
                                Sockets初始化信息
 
 //MAKEWORD(a,b)是将两个byte型合并成一个word型,一个在高8位(b),一个在低8位(a)
 if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){
 cout<<"套接字初始化失败!";
 Sleep(3000);
 exit(-1);
 }
 
 SOCKET serverSocket=StartServer();//启动服务器
 SOCKET clientSocket=ReceiveConnect(serverSocket);//接收客服端的链接
 
 FileLog fileLog;
 fileLog.Open(GetDate("%Y%m%d").append(".log").c_str());//打开记录聊天内容文件
 
 ReceiveStruct receiveStruct(&clientSocket,&fileLog);
 _beginthread(receive,0,&receiveStruct);//启动一个接收数据的线程
 
 SendMsg(clientSocket,fileLog);//发送数据
  fileLog.Close();//关闭文件closesocket(clientSocket);//关闭客户端套接字(马上发送FIN信号,所有没有接收到或是发送完成的数据都会丢失)
 closesocket(serverSocket);//关闭服务器套接字
 
 //清理套接字占用的资源
 WSACleanup();
 return0;
 }
 |   客户端代码: 
                           
                            | // Client.cpp  #include "stdafx.h"
 #include <windows.h>
 #include <process.h>
 #include <iostream>
 usingnamespace std;
 #pragma comment(lib,"ws2_32.lib")
 //接收数据void Receive(PVOID param)
 {
 char buf[2096];
 while(1)
 {
 SOCKET* sock=(SOCKET*)param;
 int bytes;
 if((bytes=recv(*sock,buf,sizeof(buf),0))==SOCKET_ERROR){
 printf("接收数据失败!\n");
 exit(-1);
 }
 buf[bytes]='\0';
 cout<<"服务器说:"<<buf<<endl;
 }
 }
 //获取服务器IPunsigned long GetServerIP(void)
 {
 //把字符串的IP地址转化为u_long
 char ipStr[20];
 //用第二个参数填充第一个参数所指的内存,填充的长度为第三个参数的大小
 memset(ipStr,0,sizeof(ipStr));
 cout<<"请输入你要链接的服务器IP:";
 cin>>ipStr;
 unsigned long ip;
 if((ip=inet_addr(ipStr))==INADDR_NONE){
 cout<<"不合法的IP地址:";
 Sleep(3000);
 exit(-1);
 }
 return ip;
 }
 //链接服务器void Connect(SOCKET &sock)
 {
 unsigned long ip=GetServerIP();
 //把端口号转化成整数
 short port=1986;
 cout<<"Connecting to "<<inet_ntoa(*(in_addr*)&ip)<<" 
                                : "<<port<<endl;
 struct sockaddr_in serverAddress;
 memset(&serverAddress,0,sizeof(sockaddr_in));
 serverAddress.sin_family=AF_INET;
 serverAddress.sin_addr.S_un.S_addr= ip;
 serverAddress.sin_port = htons(port);
 //建立和服务器的连接
 if(connect(sock,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){
 cout<<"建立连接失败:"<<WSAGetLastError();
 Sleep(3000);
 exit(-1);
 }
 }
 //发送数据void SendMsg(SOCKET &sock)
 {
 char buf[2048];
 while(1){
 
 //从控制台读取一行数据
 gets_s(buf);
 cout<<"我说:";
 //发送给服务器
 if(send(sock,buf,strlen(buf),0)==SOCKET_ERROR){
 cout<<"发送数据失败!";
 exit(-1);
 }
 }
 }
 int main(int argc, char* argv[]){WSADATA wsa;
 //初始化套接字DLL
 if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){
 cout<<"套接字初始化失败!";
 Sleep(3000);
 exit(-1);
 }
 
 //创建套接字
 SOCKET sock;
 if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){
 cout<<"创建套接字失败!";
 exit(-1);
 }
  Connect(sock);//链接服务器
 _beginthread(Receive,0,&sock);//启动接收数据线程
 SendMsg(sock);//发送数据
 
 //清理套接字占用的资源
 WSACleanup();
 return0;
 }
 |  文件操作代码(FileLog.h): 
                           
                            | #include "iostream"#include "string.h"
 #include <windows.h>
 usingnamespace std;
 class FileLog{
 private:
 CRITICAL_SECTION cs;
 HANDLE fileHandle;
 void Lock()
 {
 EnterCriticalSection(&cs);// 进入临界区
 }
  void UnLock(){
 LeaveCriticalSection(&cs);//离开临界区
 }
  public:FileLog()
 {
 InitializeCriticalSection(&cs);//初始化临界区
 fileHandle=INVALID_HANDLE_VALUE;//先初始化为错误的句柄
 }
  ~FileLog(){
 if(fileHandle!=INVALID_HANDLE_VALUE)
 {
 //CloseHandle的功能是关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄
 CloseHandle(fileHandle);
 }
 DeleteCriticalSection(&cs);//删除临界区
 }
 
 BOOL Open(constchar*fileName);//打开文件
 FileLog& Write(constchar*content);//向文件中写入内容
 FileLog& WriteLine(constchar*content);//向文件中写入内容
 BOOL Read(char*buf,int size);//读文件内容
 BOOL Close();//关闭文件
 };
 |  文件操作代码(FileLog.app): 
                           
                            | #include "stdafx.h"#include "FileLog.h"
 //打开文件
 BOOL FileLog::Open(constchar*fileName)
 {
 if(fileHandle==INVALID_HANDLE_VALUE)
 {
 fileHandle=CreateFile(fileName,GENERIC_WRITE|GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,
 OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
 if(fileHandle!=INVALID_HANDLE_VALUE)
 {
 SetFilePointer(fileHandle,0,NULL,FILE_END);
 return TRUE;
 }
 }
 return FALSE;
 }
 //写文件 返回当前对象的引用,实现连接操作FileLog& FileLog::Write(constchar*content)
 {
 Lock();
 if(fileHandle!=INVALID_HANDLE_VALUE)
 {
 DWORD dwSize=0;
 WriteFile(fileHandle,content,strlen(content),&dwSize,NULL);//写
 }
 //开始的时候少写了这句,由于加的锁没有释放,一个线程占用之后,导致其他线程只能一直等待,好久都没有找到原因。
 UnLock();
 return*this;
 }
 //写入一行FileLog& FileLog::WriteLine(constchar*content)
 {
 Lock();
 if(fileHandle!=INVALID_HANDLE_VALUE)
 {
 DWORD dwSize=0;
 WriteFile(fileHandle,content,strlen(content),&dwSize,NULL);//写
 }
 UnLock();
 return FileLog::Write("\r\n");
 }
 //读文件内容BOOL FileLog::Read(char*buf,int size)
 {
 BOOL isOK=FALSE;
 Lock();
 if(fileHandle!=INVALID_HANDLE_VALUE)
 {
 DWORD dwSize=0;
 isOK=ReadFile(fileHandle,buf,size,&dwSize,NULL);//读
 }
 return isOK;
 }
 //关闭文件BOOL FileLog::Close()
 {
 BOOL isOK=FALSE;
 Lock();
 if(fileHandle!=INVALID_HANDLE_VALUE)
 {
 isOK=CloseHandle(fileHandle);
 fileHandle=INVALID_HANDLE_VALUE;
 }
 UnLock();
 return isOK;
 }
 |  这是一个基于windows的,用C++编写的客户端服务器程序,适合初学者,高手误入.源码必共享 
 思路是这样的.启动服务器,服务器启动后会创建一个子线程,用于向客户端发送信息.用一个死循环用于接收客户端的请求,客户端请求成功后,会将客户端的连接保存到一个集合中,下面会详细介绍这个保存客户端连接的类.客户端连接成功后,服务器会创建一个子线程用于接收客户端的信息,客户端同样也会创建一个子线程接收服务器的信息.这样客户端和服务器就能进行通讯,如果有哪一方退出,另一方对应的接收数据的线程就会自动终止. 
 退出一个客户端后,服务器对应的接收数据的线程自动终止.如下图: 
 服务器保存客户端连接的集合中会删除对应的客户端连接,由于这个删除操作是在子线程中发生的,也就是说会有多个线程操作这个集合,那么针对这个集合的操作必须是线程安全的.保证线程安全的方法又很多,我的这篇博客《多线程编程--5种方法实现线程同步》介绍了5中方法实现线程同步,我这里用的是关键段,还有一点值得说明的是,保存客户端连接的集合肯定只能有一份,我用一个类封装了这个集合,这个类中的每个方法都是线程安全的,且只能有一个实例,这里用了比较暴力的方法,将相关的方法设为private,提供一个public的方法返回这个对象的一个静态实例,唯一的一个实例。 保存客户端连接的类如下: 
                           
                            | //ClientList.h 存放客户端的请求,只能有一个实例#ifndef _CLIENTLIST_H_
 #define _CLIENTLIST_H_
 #include <vector>
 #include "CSocket.h"
 #include <assert.h>
 class CSocket;
 class ClientList
 {
 public :
 typedef vector<CSocket*>::iterator Iter;
 
 void Add(CSocket* socket);
  int Count() const;  CSocket* operator[](size_t index);  void Remove(CSocket* socket);  Iter Find(CSocket* socket);   void Clear();   static ClientList* GetInstance(){
 static ClientList instance;
 return &instance;
 }
  ~ClientList();private:
 static CRITICAL_SECTION g_cs;
 static vector<CSocket*> _list;
 ClientList();
 ClientList(const ClientList&);
 ClientList& operator=(const ClientList&);
 };
 
 #endif
 |  
 
                           
                            | #include "ClientList.h"typedef vector<CSocket*>::iterator Iter;
 ClientList::ClientList(){
 InitializeCriticalSection(&g_cs);//初始化g_cs的成员
 }
 ClientList::~ClientList(){
 DeleteCriticalSection(&g_cs);//删除关键段
 }
 void ClientList::Add(CSocket* socket){
 if(socket!=NULL)
 {
 EnterCriticalSection(&g_cs);//进入关键段
 _list.push_back(socket);
 LeaveCriticalSection(&g_cs);//退出关键段
 }
 }
 int ClientList::Count() const{
 return _list.size();
 }
 CSocket* ClientList::operator[](size_t index){
 assert(index>=0 && index<_list.size());
 return _list[index];
 }
 void ClientList::Remove(CSocket* socket){
 Iter iter=Find(socket);
 EnterCriticalSection(&g_cs);//进入关键段
 if(iter!=_list.end())
 {
 delete *iter;
 _list.erase(iter);
 }
 LeaveCriticalSection(&g_cs);//退出关键段
 }
 Iter ClientList::Find(CSocket* socket){
 EnterCriticalSection(&g_cs);//进入关键段
 Iter iter=_list.begin();
 while(iter!=_list.end())
 {
 if(*iter==socket)
 {
 return iter;
 }
 iter++;
 }
 LeaveCriticalSection(&g_cs);//退出关键段
 return iter;
 }
 void ClientList::Clear(){
 EnterCriticalSection(&g_cs);//进入关键段
 for(int i=_list.size()-1;i>=0;i--)
 {
 delete _list[i];
 }
 _list.clear();
 LeaveCriticalSection(&g_cs);//退出关键段
 }
 CRITICAL_SECTION ClientList::g_cs;vector<CSocket*> ClientList::_list ;
 |  |