基于TCP 的Socket 编程

来源:互联网 发布:jsp项目绑定域名 编辑:程序博客网 时间:2024/05/18 03:33

之前老师布置了不少关于任务,都是有关程序之间的通讯的。最开始只会读写文件,自然就用的是文件进行数据交流。文件交换数据有个问题就是一个程序读或者写的时候,文件被占用,另一个程序如果继续进行读取文件,就会报错。所以我就降低读写的频率,但是还是免不了两个程序同时操作文件的时候。后来学了数据库,虽然感觉牛逼一点了,但是感觉其实还是一个意思。后来学长给了一个socket+protobuf的动态链接库,用起来确实要比文件好,传输速度快,频率还高。用文件传,频率低了两个程序之间就会有延迟,换了socket延迟问题就没有了。
这两天事情不是很多,想起把这个东西看懂。学长给的那个是用C#写的,而且加了protobuf,很多东西看不懂,而且老师要求写的东西是MFC,要在MFC用这一套东西的话,还是得弄懂它,重写一遍。所以先从简单的开始,先把这个socket看懂。找了不少资料,有个网站的东西还不错,(http://c.biancheng.net/cpp/socket/)看了之后,懂了那么些意思。
我这里的用是TCP传输协议。

socket的基本操作

1. socket()函数
在Windows下创建socket,用socket函数来创建,原型如下:

SOCKET socket(int af, int type, int protocol)
  • af (Address Family),也就是 IP 地址类型。有AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址。
  • type 为数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM。
  • protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。简单理解就是TCP会保证数据传输的到达,UDP就是发出去就不管了。
    一般情况下,有了af,type两个参数就可以确定创建套接字了。所以下面的代码,第三个参数就写成0。
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

2. bind()函数 connet()函数
1). bind()函数
创建套接字后服务器需要bind函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理。原型如下:

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  
  • sock指前面创建的套接字
  • addr 为 sockaddr 结构体变量的指针。我后面的代码写的却是sockaddr_in类型,然后又强制转换成sockaddr类型。这里为什么这么麻烦,可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体。
  • addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
sockaddr_in sockAddr;//    memset(&sockAddr, 0, sizeof(sockAddr));//每个字节用0填充    sockAddr.sin_family = PF_INET;//设置IP为IPv4类型    sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// 就写127.0.0.1 就可以了,指本地机,一般用来测试使用。    sockAddr.sin_port = htons(8888);//设置一个端口号 最好在1024~65536 之间分配端口号。    //sockaddr_in 结构体变量创建好后,要强制转换成SOCKADDR型,就可以将其与前面的套接字绑定起来。    if (-1 == bind(serSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)))    {        //这里这样写如果程序出错,可以得到错误编码。我当时就是IP地址的点写成了逗号,找了好久的错(哈哈)        DWORD err = GetLastError();        printf("bind error:%d\n", err);        return 0;    }

2). connect()函数
客户端在创建套接字之后就可以用connect()函数来建立连接了,原型为:

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  

参数的说明和 bind() 相同

3. listen()函数
作为一个服务器,在调用socket()、bind()之后就会通过 listen() 函数可以让套接字进入被动监听状态。
原型如下:

int listen(SOCKET sock, int backlog);  
  • sock 为需要进入监听状态的套接字。
  • backlog 为请求队列的最大长度。能存放多少个客户端请求。

4. accept() 函数
作为服务器套接字执行socket() bind() listen()后就处于于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);  
  • 它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
  • accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
  • accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来,就是客户端执行connect()。
    //接收客户端请求    SOCKADDR clntAddr;//客户端地址    int nSize = sizeof(SOCKADDR);    SOCKET clntSock = accept(serSock, (SOCKADDR*)&clntAddr, &nSize);//返回后面用于与客户端通信用的新套接字。

5. 数据的接收和发送

服务器与客户端的套接字建立连接后,可以调用网络I/O进行读写操作了。
我这里就只用了send() recv() 原型如下:

int send(SOCKET sock, const char *buf, int len, int flags);int recv(SOCKET sock, char *buf, int len, int flags);
  • sock 为要发送数据的套接字,buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项,设置为0就可以了。

6. 关闭连接
要关闭服务器与客户端的连接。直接调用closesocket()。参数就是前面创建的套接字。

两个程序的源码

先执行服务器程序,然后执行客户端程序
- 服务器端
执行的函数依次是socket() bind() listen() accpet() send() closesocket()

#include "stdio.h"#include "winsock2.h"#pragma comment (lib, "ws2_32.lib")//windows下这些函数写在了winsock2里,所以这里就照敲int main(){    //初始化DLL    WSADATA wsaData;    WSAStartup(MAKEWORD(2, 2), &wsaData);    //创建套接字    SOCKET serSock = socket(PF_INET, SOCK_STREAM, 0);    if (serSock == INVALID_SOCKET)    {        printf("socket error!");        return 0;    }    //绑定套接字    sockaddr_in sockAddr;    memset(&sockAddr, 0, sizeof(sockAddr));//每个字节用0填充    sockAddr.sin_family = PF_INET;//IPv4    sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// 具体IP地址    sockAddr.sin_port = htons(8888);//端口    if (-1 == bind(serSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)))    {        DWORD err = GetLastError();        printf("bind error:%d\n", err);        return 0;    }    //监听状态    listen(serSock, 20);    //等待接收客户端请求    SOCKADDR clntAddr;    int nSize = sizeof(SOCKADDR);    SOCKET clntSock = accept(serSock, (SOCKADDR*)&clntAddr, &nSize);    //向客户端发送数据    char buffer[100] = { "Hello World!" };    send(clntSock, buffer, strlen(buffer) + sizeof(char), NULL);//发送数据    //关闭套接字    closesocket(serSock);    //终止DLL 使用    WSACleanup();    return 0;}
  • 客户端
    执行的函数依次是socket() connect() recv() closesocket()
#include "stdio.h"#include "winsock2.h"#pragma comment (lib, "ws2_32.lib")int main(){    //初始化dll    WSADATA wsaData;    WORD sockVersion = MAKEWORD(2,2);    if (WSAStartup(sockVersion,&wsaData) != 0)    {        return 0;    }    //创建套接字    SOCKET clnSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    if (clnSocket == INVALID_SOCKET)    {        printf("socket error!");        return 0;    }    //绑定套接字 bind    sockaddr_in sockAddr;    sockAddr.sin_family = PF_INET;//IPv4    sockAddr.sin_port = htons(8888);//端口    sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//具体IP地址    //connet 连接服务器    connect(clnSocket, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));    //接受服务器传回的数据    char szBuffer[MAXBYTE] = { 0 };    recv(clnSocket, szBuffer, MAXBYTE, NULL);    printf("Message form server :%s\n", szBuffer);    closesocket(clnSocket);    WSACleanup();    return 0;}
原创粉丝点击