socket通信详解(附代码)

socket简介

​ 在计算机领域,socket是计算机之间进行通信的一种约定或者方式,通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

1. IP地址

本机IP地址是127.0.0.1,当需要通信时,需要将IP地址封装到要发送的数据包中,路由器可以找到目标计算机,并将数据包传递给它,完成一次单向通信。

2. 端口(Port)

​ 一台计算机可以同时提供多种网络服务,Web服务,FTP服务,SMTP服务(邮箱),仅有IP地址,计算机可以正常接收到数据包,但是不知道选取那个网络程序来处理。为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号。

​ 端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。

在这里插入图片描述

3. 协议

协议是网络通信的约定,通信双方必须都遵守才能正常收发数据。协议有很多种,例如TCP、UDP、IP等。

协议族是一组协议的统称。最常用的是TCP/IP协议族,它包含了TCP、IP、UDP、Telnet、FTP、SMTP等。由于TCP、IP是常用的底层协议,所以把他们统称TCP/IP协议族。

4. 数据传输方式

计算机之间有很多数据传输方式,常用的有SOCK_STREAM和SOCK_DGRAM.

(1) SOCK_STREAM表示面向连接的数据方式。数据可以准确无误的到达另一台计算机。如果出现损坏或者丢失,将会重新发送,因此效率较低。常见的HTTP协议就是使用SOCK_STREAM传输数据。

(2)SOCK_DGRAM表示无连接的数据传输方式。计算机只管传输数据,不做数据校验。如果传输中损坏或者丢失,无法找回,SOCK_STREAM不会频繁的丢失数据。因此效率较高。

有可能多种协议使用同一种数据传输方式,所以在socket编程中需要同时指明数据传输方式和协议。

Linux socket编程演示

需要实现的功能:客户端clien.cpp从服务器端server.cpp读取一个字符串并打印出来。

server.cpp:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet.h>

int main(){
    //通过socket()函数创建套接字
    int serv_sock = socket(AF_INET,//表示使用IPv4地址
                           SOCK_STREAM, //表示面向连接的数据传输方式
                           IPPROTO_TCP);//使用TCP协议
    //将套接字与IP、端口绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));//每个字节用0填充
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址
    serv_addr.sin_port = htons(1234);//端口
    
    //将套接字serv_sock与特定的IP地址和端口绑定,IP地址和端口都保存在sockaddr_in结构体中
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
    /*进入被动监听状态,等待用户发起请求。所谓被动监听,
    是指套接字一直处于“睡眠”中,直到客户端发起请求才会被“唤醒”*/
    listen(serv_sock, 20);
    
    //接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    //程序一旦执行到accept就会被堵塞(暂停运行),直到客户端发起请求
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, $clnt_addr_size);
    
    //向客户端发送数据
    char str[] = "Hello World";
    //向套接字写入数据
    write(clnt_sock, str, sizeof(str));
    
    //关闭套接字
    close(clnt_sock);
    close(serv_sock);
    
    return 0;
    
}

client.cpp:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    
    //向服务器发起请求(特定的IP和端口)
    struct sockaddr_in serv_addr;
    memset($serv_addr, 0, sizeof(serv_addr));//用0填充每个字节
    serv_addr.sin_family = AF_INET;//使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(1234);
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
    //读取服务器传回的数据
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);
    
    printf("Message from server: %s\n", buffer);
    
    //关闭套接字
    close(sock);
    
    return 0;
    
}

先编译server.cpp运行,正常情况下,程序运行到accept()处就会堵塞,处于监听状态,等待客户端发送请求。

接下来运行client.cpp, 通过connect()函数向server发送请求,处于监听状态的server被激活,执行accept()函数,接收客户端发送的请求,然后执行write()函数向client传回数据。client接收到传回的数据后,connect就结束了,然后在利用read()将数据读出来。

需要注意的是:

(1)本程序server只接收一次client请求,当server向client传回数据后,程序就停止了。如果想再次收到服务器的数据,必须再次运行server.

(2)server通过connect向服务器发送请求,服务器的IP地址和端口号保存在sockaddr_in结构体中。直到服务器传回数据后,connect()才结束运行。

windows socket程序演示

windows下socket程序和Linux思路相同,但细节有所差别:

(1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,将在下篇文章中讲解。

(2) Linux使用“文件描述符”概念,而Windows使用”文件句柄“的概念;Linux不区分socket文件和普通文件,而Windows区分;Linux下socket()函数的返回为int类型,而Windows下为SOCKET类型,也就是句柄。

(3) Linux下使用read()/write()函数读写,而Windows下使用recv()/send()函数进行发送和接收。

(4)关闭socket时,Linux使用close()函数,而Windows使用closesocket()函数。

server.cpp:

#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib");//加载ws2_32.dll

int main(){
    //初始化dll
 	WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), $wsaData);
    
    //创建套接字
    SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    //绑定套接字
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;  //使用IPv4地址
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    sockAddr.sin_port = htons(1234);  //端口
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    
    //进入监听状态
    listen(servSock, 20);
    
    //接收客户端请求
    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
    //向客户端发送数据
    char *str = "Hello World!";
    send(clntSock, str, strlen(str)+sizeof(char), NULL);
    //关闭套接字
    closesocket(clntSock);
    closesocket(servSock);
    //终止 DLL 的使用
    WSACleanup();
    return 0;
    
}

client.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

int main(){
    //初始化dll
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    //创建套接字
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //向服务器发起请求
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(1234);
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    
    //接收服务器传回的数据
    char szBuffer[MAXBYTE] = {0};
    recv(sock, szBuffer, MAXBYTE, NULL);
    //输出接收到的数据
    printf("Message form server: %s\n", szBuffer);
    //关闭套接字
    closesocket(sock);
    //终止使用 DLL
    WSACleanup();
    system("pause");
    return 0;
}
  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值