C语言网络编程 - Go语言中文社区

C语言网络编程


套接字

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


Linux下的套接字

Linux中创建的文件都有一个 int 类型的编号,称为文件描述符(File Descriptor)。使用文件时,只要知道文件描述符就可以。socket 也被认为是文件的一种,和普通文件的操作没有区别

Windows下的套接字

WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本:

  • 较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;
  • 最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。

加载ws2_32.dll

#pragma comment (lib, "ws2_32.lib")

WSAStartup()

使用DLL之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
  • wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号)
  • lpWSAData 为指向 WSAData 结构体的指针。

eg:

WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);


建立Socket


Linux

int socket(int af, int type, int protocol);
  • af ,地址族(Address Family),常用AF_INET(IPv4) 和 AF_INET6(IPv6)。
  • type ,数据传输方式,常用的有 SOCK_STREAM(面向连接)和 SOCK_DGRAM(无连接)
  • protocol 表示传输协议,常用的有 IPPROTO_TCP(TCP协议) 和 IPPTOTO_UDP(UDP协议)

1.TCP socket

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 

2.UDP socket

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Windows

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字


bind() 函数

将套接字与特定的IP地址和端口绑定起来

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  //Linux
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  //Windows

示例

//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
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");  //具体的IP地址
serv_addr.sin_port = htons(1234);  //端口

//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

1、sockaddr_in 结构体

struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};
  • sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。

  • sin_prot 为端口号。需要用 htons() 函数转换

  • sin_addrstruct in_addr 结构体类型的变量

    in_addr 结构体

    struct in_addr{
    in_addr_t  s_addr;  //32位的IP地址
    };
    

    需要inet_addr()转换

    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    

2、sockaddr

bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

bind() 第二个参数的类型为 sockaddr,而代码中却使用 sockaddr_in,然后再强制转换为 sockaddr,这是为什么呢?

sockaddr 结构体的定义如下:

struct sockaddr{
    sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
    char         sa_data[14];  //IP地址和端口号
};

下图是 sockaddr 与 sockaddr_in 的对比(括号中的数字表示所占用的字节数):

sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。


connect() 函数

建立连接

与bind类似

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  //Linux
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  //Windows


linsten() 函数

套接字进入被动监听状态

int listen(int sock, int backlog);  //Linux
int listen(SOCKET sock, int backlog);  //Windows
  • sock 需要进入监听状态的套接字
  • backlog 请求队列的最大长度


accept() 函数

套接字处于监听状态时,接收客户端请求,返回新的套接字

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);  //Linux
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);  //Windows
  • sock 为服务器端套接字
  • addr 为 sockaddr_in 结构体变量
  • addrlen 为参数 addr 的长度,可由 sizeof() 求得


Linux 接受和发送数据

write()

写入数据

ssize_t write(int fd, const void *buf, size_t nbytes);
  • fd 为要写入的文件的描述符
  • buf 为要写入的数据的缓冲区地址
  • nbytes 为要写入的数据的字节数。

read()

读取数据

ssize_t read(int fd, void *buf, size_t nbytes);
  • fd 为要读取的文件的描述符
  • buf 为要接收数据的缓冲区地址
  • nbytes 为要读取的数据的字节数。


Windos 接受和发送数据

send()

发送数据

int send(SOCKET sock, const char *buf, int len, int flags);
  • sock 为要发送数据的套接字
  • buf 为要发送的数据的缓冲区地址
  • len 为要发送的数据的字节数
  • flags 为发送数据时的选项,一般设置为 0 或 NULL

recv()

接受数据

int recv(SOCKET sock, char *buf, int len, int flags);

同上


示例

Windows

server

#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);
    //创建套接字 PF_INET:IPv4
    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

#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: %sn", szBuffer);
    //关闭套接字
    closesocket(sock);
    //终止使用 DLL
    WSACleanup();
    system("pause");
    return 0;

Linux

服务器端代码 server.cpp

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(){
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //将套接字和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");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    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);
    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");  //具体的IP地址
    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 form server: %sn", buffer);
   
    //关闭套接字
    close(sock);
    return 0;
}

参考

C语言网络编程_C语言中文网

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_41725312/article/details/90375742
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 16:15:09
  • 阅读 ( 1232 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢