socket基础

Exemple:
Linux:
server
#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
#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: %s\n", buffer);
 
    //关闭套接字
    close(sock);

    return 0;
}


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);

    //创建套接字
    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: %s\n", szBuffer);

    //关闭套接字
    closesocket(sock);

    //终止使用 DLL
    WSACleanup();

    system("pause");
    return 0;
}



各函数介绍:

1. 创建 socket


linux:
int socket(int af, int type, int protocol);

windows:
SOCKET socket(int af, int type, int protocol);
参数介绍:
  1. af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INETAF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。你也可以使用PF前缀,PF是“Protocol Family”的简写,它和AF是一样的。例如,PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6。
  2. type 为数据传输方式,常用的有 SOCK_STREAM(TCP) 和 SOCK_DGRAM(UDP)SOCK_STREAM:面向连接的稳定socket;SOCK_DGRAM:无保障的面向消息的socket
  3. protocol 表示传输协议,常用的有 IPPROTO_TCP IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0.  However, it is possible that many protocols may exist, in which case a particular protocol must be specified in this manner。


2.bind() and connect()  绑定地址和连接服务端


服务端需要通过bind绑定地址,客户端需要通过connect连接服务端

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

connect:
//Linux
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); 
//Windows
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); 
参数介绍:
  1. sock: socket 文件描述符,由socket()获得
  2. addr: 指向sockaddr对象的指针,
  3. addrlen: sockaddr对象大小,可通过sizeof()获得

sockaddr结构:
struct sockaddr{
  sa_family_t sin_family; //地址族(Address Family),也就是地址类型
  char sa_data[14]; //IP地址和端口号,不为ip:port形式
};
由于没有把ip:port转换成需要的格式的方式,一般通过定义sockaddr_in(IPv4),然后通过强制转换为sockaddr来使用
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后的变量值转化为字符串存入sa_data

in_addr结构:
- struct in_addr{
- in_addr_t s_addr; //32位的IP地址
- };
in_addr_t 在头文件 <netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换,例如:
unsigned long ip = inet_addr("127.0.0.1");


3. listen() and accept() 监听请求和接受请求


listen()
int listen(int sock, int backlog); //Linux
int listen(SOCKET sock, int backlog); //Windows
参数介绍:
  1. sock:  socket 文件描述符,由socket()获得
  2. backlog: 请求队列的最大长度。如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误,对于 Windows,客户端会收到 WSAECONNREFUSED 错误。
listen() 只是让套接字处于监听状态,并没有接收请求,通过accept()才开始接受请求
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);//Linux
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);//Windows
参数同bind()和connect(),addr为客户端地址
accept() 返回一个新的套接字来和客户端通信,后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。


4.数据的接受和发送


socket缓冲区
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
Linux:
ssize_t write(int fd, const void *buf, size_t nbytes);
ssize_t read(int fd, const void *buf, size_t nbytes);
Windows:
int send(SOCKET sock, const char *buf, int len, int flags);
int recv(SOCKET sock, const char *buf, int len, int flags);
参数说明:
  1. fd: 文件描述符,用于网络即为socket
  2. buf: 缓冲区
  3. nbytes,len: 缓冲区大小
  4. flags:  一般设置为 0 或 NULL, A set of flags that specify the way in which the call is made. This parameter is constructed by using the bitwise OR operator with any of the following values.
size_t 是通过 typedef 声明的 unsigned int 类型;ssize_t 在 "size_t" 前面加了一个"s",代表 signed,即 ssize_t 是通过 typedef 声明的 signed int 类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

紫无之紫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值