实习日志3socket通信

3 篇文章 0 订阅
3 篇文章 0 订阅

原理

原文链接
一、什么是TCP/IP?

    TCP提供基于IP环境下的数据可靠性传输,事先需要进行三次握手来确保数据传输的可靠性。详细的博主不再赘述,感兴趣的朋友可以去search一下。

二、什么是socket?

    socket顾名思义就是套接字的意思,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。

    socket编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前两者较常用。基于TCP的socket编程是流式套接字。

三、client/server即C/S模式:

   TCP/IP通信中,主要是进行C/S交互。废话不多说,下面看看具体交互内容:

   服务端:建立socket,申明自身的port和IP,并绑定到socket,使用listen监听,然后不断用accept去查看是否有连接。如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket。如果不需要等待任何客户端连接,那么用closeSocket直接关闭自身的socket。

    客户端:建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。

细节

1、server端
1)加载套接字库,创建套接字(WSAStartup()/socket());

 { WSADATA ws;
    WSAStartup(MAKEWORD(2, 2), &ws);
    char *clnt_buf;
    char recv_buf[MAX];
    memset(recv_buf, 'k', MAX);

    ssize_t ret;

    clnt_buf = malloc(SIZE);
    memset(clnt_buf, 'M', SIZE);
}
  start_socket();
    void start_socket() {

    if ((orig_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("generate error");
    }

//创建套接字 s_server = socket(AF_INET, SOCK_STREAM, 0);

SOCKET WSAAPI socket(
	int af, //地址族规范。 地址族的可能值在Winsock2.h头文件中定义。
	int type,//新套接字的类型规范。
	int protocol//要使用的协议。
);

memset函数及其用法(最全详解)
void *memset(void s, int ch, size_t n);:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 [1] 。
char a[20]清零,一定是 memset(a,0,20
sizeof(char));

malloc()分配所需的内存空间,并返回一个指向它的指针。char *clnt_buf;clnt_buf = malloc(SIZE);

ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int。ssize_t ret;

(2)绑定套接字到一个IP地址和一个端口上(bind());

static struct sockaddr_in clnt_adr, serv_adr;

void start_bind() {
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(PORT);

    if (bind(orig_sock, (struct sockaddr *) &serv_adr,
             sizeof(serv_adr)) < 0) {
        perror("bind error");
        clean_up(orig_sock, NAME);
    }
}

三个结构体 struct sockaddr_in, struct sockaddr, struct in_addr

 /* socket通用地址 */
 struct sockaddr {
    unsigned short sa_family;  /* 地址族, AF_xxx */
    char sa_data[14];  /* 14字节的协议地址*/
};

本次用到的网络套接字地址

/* Internet socket 地址*/
	struct sockaddr_in {
	    short int sin_family; /* 地址族 */
	    unsigned short int sin_port; /* 端口号 */
	    struct in_addr sin_addr; /* Internet地址 */
	    unsigned char sin_zero[8]; /* 与struct sockaddr一样的长度 */
	};

大佬蔑视之由 serverAdd.sin_addr.s_addr 引发的思考

typedef struct in_addr {  
       union {    
                 struct {      u_char s_b1,s_b2,s_b3,s_b4;    } S_un_b;    
                 struct {      u_short s_w1,s_w2;    } S_un_w;    
                 u_long S_addr;  
                } S_un;
} in_addr;

最后,struct in_addr就是32位IP地址

struct in_addr {
    unsigned long s_addr;
};

ntohs, ntohl, htons,htonl的比较和详解

htons() 将主机的无符号短整形数转换成网络字节顺序。//将无符号短整型主机字节序转换为网络字节序
htonl() 将主机的无符号长整形数转换成网络字节顺序。//将无符号长整型网络字节序转换为主机字节序
均返回一个网络字节顺序的值。

int bind(
	int sockfd,1)参数 sockfd ,需要绑定的socket。
	const struct sockaddr *addr,2)参数 addr ,存放了服务端用于通信的地址和端口。
	socklen_t addrlen 				(3)参数 addrlen ,表示 addr 结构体的大小
);4)返回值:成功则返回0 ,失败返回-1,错误原因存于 errno 中。如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错,否则一般不会返回错误。

void perror(const char *str):str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。

(3)将套接字设置为监听模式等待连接请求(listen());

void start_listen() {
    listen(orig_sock, 5);
}

socket编程:listen()函数详解
int listen(
In SOCKET s, //已绑定但未监听的套接字
In int backlog //挂起连接队列的最大长度
);//监听套接字标识符

成功返回0
失败返回SOCKET_ERROR
具体错误码:WSAGetLastError()
释放:closesocket(socketListen);
清理:WSACleanup();

(4)请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());

void start_accept() {
    clnt_len = sizeof(clnt_adr);
    if ((new_sock = accept(orig_sock, (struct sockaddr *) &clnt_adr, &clnt_len)) < 0) {
        perror("accept error");
        clean_up(orig_sock, NAME);
    }
}

sizeof是C语言的一种单目操作符,如C语言的其他操作符++、–等。
sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。它并不是函数。
sizeof()运算符的值的类型是size_t而不是int。然后在printf里用%zu作为size_t的格式。32位可以用%d 或 %u,64位可以用%lld 或 %llu
C语言:关于sizeof返回值的理解

SOCKET WSAAPI accept(
  SOCKET   s,            // s包含的是服务器的ip和port信息 。
  sockaddr *addr,       //addr用于存放客户端的地址
  int      *addrlen        //addrlen在调用函数时被设置为addr指向区域的长度,在函数调用结束后被设置为实际地址信息的长度。
);//返回值是一个新的套接字描述符

(5)用返回的套接字和客户端进行通信(send()/recv());

 for (;;) {
//        int flags = fcntl(new_sock, F_GETFL, 0);
//        flags &= ~O_NONBLOCK;
//        fcntl(new_sock, F_SETFL, flags);

//        read(new_sock, &recv_buf[0], 1); // Linux
        recv(new_sock, &recv_buf[0], 1, 0); // Windows

//        printf("recv_buf[0]  ->XX%cXX\n",recv_buf[0] );
//        printf("recv_buf[2]  ->XX%cXX\n",recv_buf[2] );
//        printf("recv_buf[5]  ->XX%cXX\n",recv_buf[5] );


        while (recv_buf[0] == '1') {
            ret = 0;
            do {
//                ret += write(new_sock, clnt_buf, SIZE - ret); // Linux
                ret += send(new_sock, clnt_buf, SIZE - ret, 0);  // Windows
                printf("write %zu bytes\n", ret);
            } while (ret != SIZE);
//            read(new_sock, &recv_buf, 1); // Linux
            recv(new_sock, &recv_buf[0], 1, 0); // Windows
        }
        printf("Client ask me to close\n");
        close(new_sock);
        printf("Wait for another connection\n");
        start_accept();
    }

(6)返回,等待另一个连接请求;

(7)关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

{   close(new_sock);
    free(clnt_buf);
    clean_up(orig_sock, NAME);
    return 0;
}
void clean_up(int sd, const char *the_file) {
    close(sd);
    unlink(the_file);
}

2、Client端

  1. 加载套接字库,创建套接字(WSAStartup()/socket);
  2. 向服务器发出连接请求(connect());
  3. 和服务器进行通信(send()/recv());
  4. 关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())

#include <winsock2.h>

typedef int socklen_t;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


const char *NAME = "./my_sock";
#define MSG "Hello world!"
#define MAX 1024
const int PORT = 9535;

int orig_sock;

//int main()
int socket_init(void) {
    struct sockaddr_in serv_adr;
//	struct hostent *host;

//	host = gethostbyname("localhost");

    if ((orig_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("generate error");
        return 1;
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr("47.108.239.67");
//	memcpy(&serv_adr.sin_addr, host->h_addr, host->h_length);
    serv_adr.sin_port = htons(PORT);

    if (connect(orig_sock, (struct sockaddr *) &serv_adr,
                sizeof(serv_adr)) < 0) {
        perror("connect error");
        return 2;
    }

    return 0;
}

//#define SIZE 1048576*8
#define SIZE 1024

int main() {

    WSADATA ws;
    WSAStartup(MAKEWORD(2, 2), &ws);
//	char buf[MAX];
    char *buf;
    ssize_t ret = 0;
    FILE *fp = fopen("a.bin", "wb");;

    buf = malloc(SIZE);

    socket_init();


    buf[0] = '1';
    printf("Sending a request\n");

//    write(orig_sock, &buf[0], 1);   // Linux
    send(orig_sock,&buf[0], 1, 0); // Windows

    printf("Waiting for server...\n");
    do {
//        ret += read(orig_sock, buf + ret, SIZE - ret);  // Linux
        ret += recv(orig_sock, buf + ret, SIZE - ret, 0);  // Windows
    } while (ret != SIZE);
    printf("read total: %zu bytes\n", ret);
    fwrite(buf, SIZE, 1, fp);

    buf[0] = '0';
    printf("Sending end request, bye~\n");
//    write(orig_sock, &buf[0], 1); // Linux
    send(orig_sock, &buf[0], 1,0);

    printf("\n");
    close(orig_sock);
    free(buf);
    fclose(fp);
    getchar();
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值