今天抽空看了一些简单的东西,主要是对服务器server和客户端client的简单实现。
面向连接的server和client,其工作流程如下图所示:
服务器和客户端将按照这个流程就行开发。。(个人觉得:通过这个流程图,Server应该要先于Client启动,不然Client的connect函数的执行就会出错啦,不知道我的个人感觉对不对,后面试试就知道了。。O(∩_∩)O~)
注意:上图的Server和Client的工作流程是基于面向有连接通信的工作流程,如果是无连接的通信,则不必调用listen和accept。 在无连接的通信中,Server调用recvfrom函数来接收消息
在编写服务器和客户端之前,需要对TCP状态有所了解。。在server和client通信之间,二者都是通过发送/接收不同的信号来改变自己的状态,其tcp状态转换图如下:
了解了二者的开发流程,就可以通过接口来具体实现。
简单的server代码实现:
1 #include"winsock2.h" 2 #include<iostream> 3 using namespace std; 4 //This line is very important 5 6 #pragma comment(lib,"ws2_32.lib") 7 int main() 8 { 9 WSADATA wsaData; 10 SOCKET ListeningSocket; 11 SOCKET NewConnection; 12 SOCKADDR_IN ServerAddr; 13 SOCKADDR_IN ClientAddr; 14 int ClientAddrLen; 15 int Port = 5150; 16 int Ret; 17 char DataBuffer[1024]; 18 19 if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0) 20 { 21 cout<<"WSAStartup failed with error "<<Ret<<endl; 22 //here no WSACleanup,because we do not create anything; 23 return -1; 24 } 25 26 // Create a new socket to listening for client connections. 27 ListeningSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 28 if ( INVALID_SOCKET == ListeningSocket) 29 { 30 cout<<"Socket failed with error "<<WSAGetLastError()<<endl; 31 WSACleanup(); 32 return -1; 33 } 34 35 ServerAddr.sin_family = AF_INET; 36 ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); 37 ServerAddr.sin_port = htons(Port); 38 39 //to bind 40 if (bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR) 41 { 42 cout<<"Binding failed with error "<<WSAGetLastError()<<endl; 43 closesocket(ListeningSocket); 44 WSACleanup(); 45 return -1; 46 } 47 48 // Listen for client connections. We used a backlog of 5 which is 49 // normal for many applications. 50 51 if (listen(ListeningSocket,5) == SOCKET_ERROR) 52 { 53 cout<<"Listen failed with error "<<WSAGetLastError()<<endl; 54 closesocket(ListeningSocket); 55 WSACleanup(); 56 return -1; 57 } 58 59 cout<<"** We are waiting for a connection on port "<<Port<<"**"<<endl; 60 61 //accep a connection when one arrives 62 63 NewConnection = accept(ListeningSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen); 64 if (INVALID_SOCKET == NewConnection) 65 { 66 cout<<"Accept failed with error "<<WSAGetLastError()<<endl; 67 closesocket(ListeningSocket); 68 WSACleanup(); 69 return -1; 70 } 71 72 cout<<"** We successfully got a connection from "<<inet_ntoa(ClientAddr.sin_addr) 73 <<":port "<<ntohs(ClientAddr.sin_port)<<"!!**"<<endl; 74 75 closesocket(ListeningSocket); 76 cout<<"** We are waiting for data...** "; 77 78 Ret = recv(NewConnection,DataBuffer,sizeof(DataBuffer),0); 79 if (SOCKET_ERROR == Ret) 80 { 81 cout<<"Recv failed with error "<<WSAGetLastError()<<endl; 82 closesocket(NewConnection); 83 WSACleanup(); 84 return -1; 85 } 86 87 cout<<"**We have successfully recieve "<<Ret<<" Byte(s) data!** "; 88 89 cout<<"**We are going to close the client connection...** "; 90 91 closesocket(NewConnection); 92 WSACleanup(); 93 94 return 0; 95 }
客户端的实现:
1 #include"winsock2.h" 2 #include<iostream> 3 using namespace std; 4 //This line is very important 5 6 #pragma comment(lib,"ws2_32.lib") 7 int main(int argc, char **argv) 8 { 9 WSADATA wsaData; 10 SOCKET s; 11 SOCKADDR_IN ServerAddr; 12 int Port = 5150; 13 int Ret; 14 15 if (argc <= 1) 16 { 17 cout<<"USAGE: tcpclient <Server IP address>. "; 18 return -1; 19 } 20 21 // Initialize Winsock version 2.2 22 23 if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0) 24 { 25 cout<<"WSAStartup failed with error "<<Ret<<endl; 26 return -1; 27 } 28 29 // Create a new socket to make a client connection. 30 31 s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 32 if (INVALID_SOCKET == s) 33 { 34 cout << "socket failed with error " << WSAGetLastError()<<endl; 35 WSACleanup(); 36 return -1; 37 } 38 39 ServerAddr.sin_family = AF_INET; 40 ServerAddr.sin_port = htons(Port); 41 ServerAddr.sin_addr.s_addr = inet_addr(argv[1]); 42 43 // Make a connection to the server with socket s. 44 45 cout<< "We are trying to connect to " << inet_ntoa(ServerAddr.sin_addr) 46 << ":" << htons(ServerAddr.sin_port) << "... "; 47 48 if (connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)) 49 == SOCKET_ERROR) 50 { 51 cout << "connect failed with error " << WSAGetLastError() << endl; 52 closesocket(s); 53 WSACleanup(); 54 return -1; 55 } 56 57 cout << "Our connection succeeded. "; 58 59 60 cout << "We will now try to send a hello message. "; 61 62 if ((Ret = send(s, "Hello", 5, 0)) == SOCKET_ERROR) 63 { 64 cout << "send failed with error " << WSAGetLastError()<<endl; 65 closesocket(s); 66 WSACleanup(); 67 return -1; 68 } 69 70 cout << "We successfully sent " << Ret << " byte(s). "; 71 72 // When you are finished sending and receiving data on socket s, 73 // you should close the socket. 74 75 cout << "We are closing the connection. "; 76 77 closesocket(s); 78 79 // When your application is finished handling the connection, call 80 // WSACleanup. 81 82 WSACleanup(); 83 return 0; 84 }
咦,奇了怪了,按照书上的代码,执行起来居然报错。。这是咋回事啊。。
运行Server时,出现这样的错误:
通过报错信息可以知道是accept函数出错了。
那就对症下药,哪儿错儿改哪儿。。
上网查找资料,找到accept函数定义:
SOCKET accept(
__in SOCKET s,
__out struct sockaddr *addr,
__inout int *addrlen
);
第一个参数就是套接字描述符,第二个参数是,接受客户端基本信息的结构体,第三参数很重要,是准备接受结构体的大小,上面的程序 int ClientAddrLen;传进去的时候只是把未知的ClientAddrLen的地址传进去,要传进去的应该是接收这些信息的基本大小啊,所以 得加ClientAddrLen = sizeof(SOCKADDR);。这样程序就执行accept。
注意:客户端需要在服务器运行的时候才能执行,不然会出错的。。
运行Server和Client(需要ip作为main函数的参数,不会的自己百度怎样在VS2008中给main传递参数),二者可以连接了。。执行还没有实现二者的通信。。。呜呜~~~~(>_<)~~~~
拓展:
观察Server中的recv函数和Client中send函数,我想是否可以利用无限循环的方式在Client端输入字符串,在Server端打印该字符串?----就像聊天一样,你输入一句,我这边就显示这一句。。。只是这样只能是单工方式的传输,不能从Server发送到Client端。。那是否可以考虑多线程呢,一个线程运行Server,一个线程运行Client。。??