实现的单个socket例子,了解socket原理。
先创建一个win32的项目(命令行的),作为服务端
// SocketServer.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include "winsock2.h"#pragma comment(lib, "ws2_32.lib")#include <iostream>using namespace std;int _tmain(int argc, _TCHAR* argv[]){ const int BUF_SIZE = 64; WSADATA wsd; //wsdata 变量 SOCKET sServer; //服务器套接字 SOCKET sClient; //客户端套接字 SOCKADDR_IN addrServ; // 服务器地址 char buf[BUF_SIZE]; //接受数据缓存区 char sendBuf[BUF_SIZE]; //返回给客户端数据 int retVal; //返回值//初始化套接字动态库
if(WSAStartup(MAKEWORD(2,2),&wsd) != 0) { cout << "wsastartup failed!" <<endl; return 1; } //创建套接字 sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(INVALID_SOCKET == sServer) { cout << "socket failed" <<endl; WSACleanup();//释放套接字 return -1; }//服务器套接字地址
addrServ.sin_family = AF_INET; addrServ.sin_port = htons(4999); addrServ.sin_addr.s_addr = INADDR_ANY;//绑定套接字
retVal = bind(sServer,(LPSOCKADDR)&addrServ,sizeof(SOCKADDR_IN)); if(SOCKET_ERROR == retVal) { cout << "bind failed" <<endl; closesocket(sServer);//关闭套接字 WSACleanup(); return -1; } //开始监听 retVal = listen(sServer,1); if(SOCKET_ERROR == retVal) { cout << "listen failed" <<endl; closesocket(sServer); WSACleanup(); return -1; } cout << "开始监听,请链接..." << endl;//接受客户端请求
sockaddr_in addrClient; int addrClientlen = sizeof(addrClient); sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen); if(INVALID_SOCKET == sClient) { cout << "accept failed!" <<endl; closesocket(sServer);//关闭套接字 WSACleanup(); return -1; } cout << "客户端链接成功..." << endl; while (true) { //接受客户端数据 ZeroMemory(buf, BUF_SIZE); retVal = recv(sClient, buf, BUF_SIZE, 0); if(SOCKET_ERROR == retVal) { closesocket(sServer);//关闭套接字 closesocket(sClient);//关闭套接字 WSACleanup(); return -1; } if(buf[0] == '0') break;cout << "客户端发送的数据:" << buf <<endl;
cout << "向客户端发送数据"; cin>>sendBuf; send(sClient,sendBuf,strlen(sendBuf), 0); }closesocket(sServer);
closesocket(sClient); WSACleanup();return 0;
}
再创建一个项目作为客户端,
// SocketClient.cpp : 定义控制台应用程序的入口点。
//客户端//#include "stdafx.h"
#include "winsock2.h"#include <iostream>#pragma comment(lib, "ws2_32.lib")using namespace std;BOOL RecvLine(SOCKET s, char *buf);//读取一行数据
int _tmain(int argc, _TCHAR* argv[])
{ const int BUF_SIZE = 64; WSADATA wsd; //WSADATA变量 SOCKET sHost; //服务器套接字 SOCKADDR_IN servAddr; //服务器地址 char buf[BUF_SIZE]; // char bufRecv[BUF_SIZE]; int retVal;//初始化套接字动态
if(WSAStartup(MAKEWORD(2,2),&wsd) != 0) { cout << "WSAStartup failed" << endl; return -1; }//创建套接字
sHost = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP); if(INVALID_SOCKET == sHost) { cout << "socket failed" <<endl; WSACleanup(); return -1; } //设置服务器地址 servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); servAddr.sin_port = htons((short)4999); int nServAddlen = sizeof(servAddr);//连接服务器
cout<< "连接服务器" << endl; retVal = connect(sHost,(LPSOCKADDR)&servAddr, nServAddlen); if(retVal == SOCKET_ERROR) { cout << "connect failed" << endl; closesocket(sHost); WSACleanup(); return -1; } cout<< "connect success" << endl; while (true) { //向服务器发送数据 ZeroMemory(buf, BUF_SIZE); cout << "向服务器发送数据:" ; cin >> buf; retVal = send(sHost,buf, strlen(buf),0); if(retVal == SOCKET_ERROR) { cout << "send failed" << endl; closesocket(sHost); WSACleanup(); return -1; } recv(sHost, bufRecv, 5,0);//接受服务器数据,只接5个字符 cout << endl << "从服务器接受数据:" << bufRecv; } //退出 closesocket(sHost); WSACleanup(); system("pause"); return 0;}
然后运行服务端,运行客户端,之间进行通信。
备注:
创建套接字的函数是socket(),函数原型为:
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);
其中“int domain”参数表示套接字要使用的协议簇,协议簇的在“linux/socket.h”里有详细定义,常用的协议簇:
- AF_UNIX(本机通信)
- AF_INET(TCP/IP – IPv4)
- AF_INET6(TCP/IP – IPv6)
其中“type”参数指的是套接字类型,常用的类型有:
- SOCK_STREAM(TCP流)
- SOCK_DGRAM(UDP数据报)
- SOCK_RAW(原始套接字)
最后一个“protocol”一般设置为“0”,也就是当确定套接字使用的协议簇和类型时,这个参数的值就为0,但是有时候创建原始套接字时,并不知道要使用的协议簇和类型,也就是domain参数未知情况下,这时protocol这个参数就起作用了,它可以确定协议的种类。
socket是一个函数,那么它也有返回值,当套接字创建成功时,返回套接字,失败返回“-1”,错误代码则写入“errno”中。
创建套接字:
#include <sys/types.h> #include <sys/socket.h> #include <linux/socket.h> int sock_fd_tcp; int sock_fd_udp; sock_fd_tcp = socket(AF_INET, SOCK_STREAM, 0); sock_fd_udp = socket(AF_INET, SOCK_DGRAM, 0); if(sock_fd_tcp < 0){ perror("TCP SOCKET ERROR!\n"); exit(-1); } if(sock_fd_udp < 0){ perror("UDP SOCKET ERROR!\n"); exit(-1); }
什么是Socket?举一个例子:Lewis跟Nico两人聊QQ,QQ是一个独立的应用程序,那么它对应了两个Socket,一个在Lewis的电脑上,一个在Nico的电脑上。当Lewis对Nico说:”周末我们去开卡丁车吧!“,这句话就是一段数据,这段数据会先储存在Lewis电脑Socket上,我们在”“一文中提到过,TCP存在于传输层,同时,我们在”“一文中又提到了TCP传输过程(三次握手建立连接,三次握手关闭连接),当Lewis的QQ和Nico的QQ连接成功后,Lewis的Socket将这段话的数据发送到Nico的电脑中,但是Nico暂时还没看到,因为数据会先存放在Nico电脑的Socket当中,然后Socket会把数据呈现给Nico看。
到了这里不禁要问,数据传送过程中为什么要多出Socket这样东西?
答:因为不同的应用程序对应不同的Socket,而Socket保证了QQ的数据不会到处乱跑,不会一冲动跑到MSN上去了。因为QQ和MSN两个应用程序的Socket内容是完全不同的。那么Socket里面到底是什么?
答:Socket套接字地址!套接字地址是一个数据结构,我们仅基于TCP传输协议作为例子。套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4种数据。而它的数据结构原型为:
#include <netinet/in.h>
struct sockaddr_in{ unsigned short sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; };其中:
- sin_family表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;
- sin_prot表示端口号,例如:21 或者 80 或者 27015,总之在0 ~ 65535之间;
- sin_addr表示32位的IP地址,例如:192.168.1.5 或 202.96.134.133;
- sin_zero表示填充字节,一般情况下该值为0;
Socket数据的赋值实例:
struct sockaddr_in Lewis;
Lewis.sin_family = AF_INET; Lewis.sin_port = htons(80); Lewis.sin_addr.s_addr = inet_addr("202.96.134.133"); memset(Lewis.sin_zero,0,sizeof(Lewis.sin_zero));分析:我们设置了一个名叫Lewis的套接字地址,它基于TCP/IP协议,因此sin_family的值为AF_INET,这个是雷打不动的,只要使用TCP/IP协议簇,该值就是AF_INET;htons是端口函数,以后介绍,这就表示设置了端口号为80;
sin_addr是一个数据结构,原型是:
struct in_addr{ unsigned long s_addr;};
因此,Lewis这个套接字地址的IP赋值格式是Lewis.sin_addr.s_addr,inet_addr函数也是日后再说,这里表示设置IP地址为202.96.134.133;而memset函数在这里起到给sin_zero数组清零的作用,它的原型是:
memset(void *s, int c, size_t n);
int listen( SOCKET s, int backlog); 第2个参数,是侦听队列的长度,也就是同时接受连接的个数,不是已经连接socket的个数 也就是listen接收到了连接,还没使用accpet来创建的连接, 比如设置为5,你接收到了5个请求,但是都没用accept来创建连接,则,第6个人连接你的时候,会连不上. 只有你调用因此accept创建一个连接,则队列里的个数减1,则又可以接受一个新连接了,第6个人就可以请求连接了. listen只是监测连接请求(维护一个队列),accept才是真正创建请求 backlog 用于在TCP层接收链接的缓冲池的最大个数,这个个数可在应用层中的listen函数里设置,当客户链接请求大于这个个数(缓冲池满),其它的未进入链接缓冲池的客户端在tcp层上tcp模块会自动重新链接,直到超时(大约57秒后) 2)应用层链接(connect)完成时,要从tcp层的链接缓冲池中移出一个(accept函数实现) 3.backlog是连接请求队列的最大长度 1.在WinSock1.1中最大值5。如果backlog小于1,则backlog被置为1;若backlog大于SOMAXCONN(定义在winsock.h中,值为5),则backlog被置为SOMAXCONN。 2.在WinSock2中,没有制定具体值,它由服务提供者决定 3.有时候backlog设置很小,这时我们接进多少台机器都没问题是因为服务器机器处理速度很快队列来不及填满就处理完了,而且在同一个时刻到来的连接还是很少的 参考:
参考: