基于TCP 的Socket 编程

之前老师布置了不少关于任务,都是有关程序之间的通讯的。最开始只会读写文件,自然就用的是文件进行数据交流。文件交换数据有个问题就是一个程序读或者写的时候,文件被占用,另一个程序如果继续进行读取文件,就会报错。所以我就降低读写的频率,但是还是免不了两个程序同时操作文件的时候。后来学了数据库,虽然感觉牛逼一点了,但是感觉其实还是一个意思。后来学长给了一个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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值