做网络通信作业之前的学习 !(>。<)!
一.TCP
1.服务端流程
1.创建socket套接字
socket套接字可以理解成网络接口,只有通过了socket套接字才能跟对应的电脑进行通信
2.给这个socket绑定一个端口号
IP地址是指定电脑的 端口号是指定电脑上面某个软件的
3.给socket开启监听属性
这个socket只能用来接收连接 不能用来做通讯
4.等待客户端连接
5.开始通讯
6.关闭连接
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")int main()
{//windows上使用网络功能需要开始网络权限WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);//1.创建socket套接字/*socket(int af, //协议地址簇 ipv4/ipv6 对应 AF_INET/AF_INET6int type, //类型 流式协议/帧式协议 对应 SOCK_STREAM/SOCK_DGRAMint protocol //保护协议 tcp/udp不用填保护协议 直接填0);*///此处是服务端socket,所以我们一般起名叫监听socketSOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCKET是一个无符号的长整型,就是一个整数,可以直接打印出来//未开启网络结果为-1 即无效的socket -> INVALID_SOCKETif (INVALID_SOCKET == listen_socket){printf("create listen socket failed !!! errocode: %d\n", GetLastError());return -1;}//2.给这个socket绑定一个端口号//struct sockaddr_in {// ADDRESS_FAMILY sin_family; //协议地址簇// USHORT sin_port; //端口号// IN_ADDR sin_addr; //IP地址// CHAR sin_zero[8]; //保留字节 -> 协议升级会用//};//大小端问题:// 一个数字在计算机中存储以二进制存储,对端口来讲占两个字节// 编程中最小单位我们通常用字节来算,很少用位算// 存数据的时候会有先后的问题 8080 -> 1F90(大端序号,高位放前面【千百个十】)// 但是本地电脑的存储方式是以小端序存储的【个十百千】//unsigned short a = 8080;//unsigned short* p = &a;struct sockaddr_in local = { 0 };local.sin_family = AF_INET;local.sin_port = htons(8080); //大小端问题 中间设备使用的是大端序//服务端 选项 网卡 127.0.0.1(本地环回)只接受哪个网卡的数据//一般写0.0.0.0 不管哪个数据库来 只要有我都接受//INADDR_ANY整数 占4个字节//local.sin_addr.s_addr = htonl(INADDR_ANY);//手动指定local.sin_addr.s_addr = inet_addr("0.0.0.0"); //字符串IP转换成整数IP//绑定 给上面定义的socket绑定我们指定的内容/*int bind(SOCKET s, //对哪个socket进行绑定const struct sockaddr FAR * name, //sockaddr的结构int namelen //长度); 返回整数类型*///为什么使用sockaddr结构而不是sockaddr_in结构?(两个结构体的大小一模一样)/*struct sockaddr {ADDRESS_FAMILY sa_family;CHAR sa_data[14];} 用这个方便扩展 通用的结构*/ if (-1 == bind(listen_socket, (struct sockaddr*)&local, sizeof(local))) {printf("bind socket failed !!! errocode: %d\n", GetLastError());return -1;}//3.给socket开启监听属性/*int listen(SOCKET s,int backlog //半连接队列长度);*/if (-1 == listen(listen_socket, 10)) {printf("start listen socket failed !!! errocode: %d\n", GetLastError());return -1;}//以上准备工作结束//4.等待客户端连接//返回的客户端socket才是跟客户端可以通讯的一个socket//listen_socket唯一的作用就是等待连接 最后返回一个socket出来//accept()是阻塞函数,等到有客户端连接进来就接受连接,然后染回,否则就阻塞/*SOCKET accept(SOCKET s, //监听socketstruct sockaddr * addr, //客户端的IP地址和端口号int * addrlen //结构的大小 为什么是指针 因为可填可不填 要填就要和上面的addr都填);*///如果要跟多个客户端通讯 需要有while循环while (1) {SOCKET client_socket = accept(listen_socket, NULL, NULL);if (INVALID_SOCKET == client_socket)continue;//5.开始通讯(B/S) //通过浏览器访问一个网站的时候 //浏览器会发送一个http请求 http是tcp的上层协议 所以写tcp是可以接收http的报文的//接收发送的报文char buffer[1024] = { 0 };/*int recv(SOCKET s, //客户端socketchar* buf, //接受的数据存到哪里int len, //接受的长度int flags //0);*/recv(client_socket, buffer, 1024, 0);printf("%s\n", buffer);//6.关闭连接closesocket(client_socket);}return 0;
}
错误码查找
大小端问题
中间设备使用的是大端序(路由器) 要从主机转换成中间件能使用的 -> htons()
不安全问题:把安全开发关掉
程序一旦弹出
代表监听已经开启了
模拟时输入127.0.0.1:8080 可以收到数据 --> tcp连接没问题
2.客户端流程
1.创建socket套接字(通过套接字连接到服务端)
2.连接服务器
3.开始通讯
4.关闭连接
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")int main()
{WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);//1.创建socket套接字(通过套接字连接到服务端)SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);if (INVALID_SOCKET == client_socket) {print("create socket failed!!!\n");return -1;}//2.连接服务器struct sockaddr_in target;target.sin_family = AF_INET;target.sin_port = htons(8080);target.sin_addr.s_addr = inet_addr("127.0.0.1");if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target))){printf("connet server failed !!!\n");closesocket(client_socket);return -1;}//3.开始通讯 send recv//回显服务器 -> 发送什么会回复什么while (1){char sbuffer[1024] = { 0 };printf("please enter: ");scanf("%s", sbuffer);send(client_socket, sbuffer, strlen(sbuffer), 0);//立马接收char rbuffer[1024] = { 0 };int ret = recv(client_socket, rbuffer, 1024, 0);if (ret <= 0) break;printf("%s\n", rbuffer);}//4.关闭连接closesocket(client_socket);return 0;
}
tcp服务器客户端的重点在于服务端
因为存在的问题比较多:发送的消息怎么进行处理,如何同时接收多个客户端进行连接
3.实现多线程的客户端
服务端不应该主动断开连接而是由客户端断开
//连接一旦完成进入线程
//Windows当中创建线程
createThread(NULL,0,thread_func, &client_socket);
存在问题因为client_socket在栈区,所以可能socket销毁而线程都还没有启动(线程的运行是抢时间片的步骤,有可能线程在启动的时刻没抢过主线程,主线程已经把客户端socket销毁了)
所以我们要单独创建一个客户端socket,单独申请一个内存
服务端代码更改
#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")//定义一个线程函数 当做线程启动入口
DWORD WINAPI thread_func(LPVOID lpThreadParameter)
{//传入client_socket 解引用拿到后 释放地址SOCKET client_socket = *(SOCKET*)lpThreadParameter;free(lpThreadParameter);while (1) {//5.开始通讯(B/S) //通过浏览器访问一个网站的时候 //浏览器会发送一个http请求 http是tcp的上层协议 所以写tcp是可以接收http的报文的//接收发送的报文char buffer[1024] = { 0 };/*int recv(SOCKET s, //客户端socketchar* buf, //接受的数据存到哪里int len, //接受的长度int flags //0);*/int ret = recv(client_socket, buffer, 1024, 0);if (ret <= 0) break;printf("%llu:%s\n",client_socket, buffer);//回显 接收完之后立马发送回去send(client_socket, buffer, (int)strlen(buffer), 0);}//断开连接 打印一下printf("socket: %llu,disconnect.\n", client_socket);//6.关闭连接closesocket(client_socket);return 0;
}int main()
{//windows上使用网络功能需要开始网络权限WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);//1.创建socket套接字/*socket(int af, //协议地址簇 ipv4/ipv6 对应 AF_INET/AF_INET6int type, //类型 流式协议/帧式协议 对应 SOCK_STREAM/SOCK_DGRAMint protocol //保护协议 tcp/udp不用填保护协议 直接填0);*///此处是服务端socket,所以我们一般起名叫监听socketSOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCKET是一个无符号的长整型,就是一个整数,可以直接打印出来//未开启网络结果为-1 即无效的socket -> INVALID_SOCKETif (INVALID_SOCKET == listen_socket) {printf("create listen socket failed !!! errocode: %d\n", GetLastError());return -1;}//2.给这个socket绑定一个端口号//struct sockaddr_in {// ADDRESS_FAMILY sin_family; //协议地址簇// USHORT sin_port; //端口号// IN_ADDR sin_addr; //IP地址// CHAR sin_zero[8]; //保留字节 -> 协议升级会用//};//大小端问题:// 一个数字在计算机中存储以二进制存储,对端口来讲占两个字节// 编程中最小单位我们通常用字节来算,很少用位算// 存数据的时候会有先后的问题 8080 -> 1F90(大端序号,高位放前面【千百个十】)// 但是本地电脑的存储方式是以小端序存储的【个十百千】//unsigned short a = 8080;//unsigned short* p = &a;struct sockaddr_in local = { 0 };local.sin_family = AF_INET;local.sin_port = htons(8080); //大小端问题 中间设备使用的是大端序//服务端 选项 网卡 127.0.0.1(本地环回)只接受哪个网卡的数据//一般写0.0.0.0 不管哪个数据库来 只要有我都接受//INADDR_ANY整数 占4个字节//local.sin_addr.s_addr = htonl(INADDR_ANY);//手动指定local.sin_addr.s_addr = inet_addr("0.0.0.0"); //字符串IP转换成整数IP//绑定 给上面定义的socket绑定我们指定的内容/*int bind(SOCKET s, //对哪个socket进行绑定const struct sockaddr FAR * name, //sockaddr的结构int namelen //长度); 返回整数类型*///为什么使用sockaddr结构而不是sockaddr_in结构?(两个结构体的大小一模一样)/*struct sockaddr {ADDRESS_FAMILY sa_family;CHAR sa_data[14];} 用这个方便扩展 通用的结构*/if (-1 == bind(listen_socket, (struct sockaddr*)&local, sizeof(local))) {printf("bind socket failed !!! errocode: %d\n", GetLastError());return -1;}//3.给socket开启监听属性/*int listen(SOCKET s,int backlog //半连接队列长度);*/if (-1 == listen(listen_socket, 10)) {printf("start listen socket failed !!! errocode: %d\n", GetLastError());return -1;}//以上准备工作结束//4.等待客户端连接//返回的客户端socket才是跟客户端可以通讯的一个socket//listen_socket唯一的作用就是等待连接 最后返回一个socket出来//accept()是阻塞函数,等到有客户端连接进来就接受连接,然后染回,否则就阻塞/*SOCKET accept(SOCKET s, //监听socketstruct sockaddr * addr, //客户端的IP地址和端口号int * addrlen //结构的大小 为什么是指针 因为可填可不填 要填就要和上面的addr都填);*///如果要跟多个客户端通讯 需要有while循环while (1){SOCKET client_socket = accept(listen_socket, NULL, NULL);if (INVALID_SOCKET == client_socket)continue;//有新的连接产生 打印一下printf("new connnet, socket: %llu\n", client_socket);//单独申请一个客户端SOCKET* sockfd = (SOCKET*)malloc(sizeof(SOCKET));*sockfd = client_socket;//连接一旦完成进入线程//Windows当中创建线程CreateThread(NULL,0,thread_func, sockfd, 0, NULL);}return 0;
}
代码实现
二.UDP
1.TCP与UDP服务端的不同之处
-
Socket类型:
- TCP使用
SOCK_STREAM
类型,而UDP使用SOCK_DGRAM
类型。
- TCP使用
-
连接方式:
- TCP是面向连接的,需要通过
connect
、listen
和accept
函数建立连接。 - UDP是无连接的,不需要建立连接,直接使用
sendto
和recvfrom
函数发送和接收数据。
- TCP是面向连接的,需要通过
-
数据传输:
- TCP提供可靠的、有序的数据传输,保证数据完整性。
- UDP提供不可靠的、无序的数据传输,可能丢包,但速度更快。
-
并发处理:
- TCP服务端通常需要为每个客户端连接创建一个线程或使用非阻塞I/O来处理并发。
- UDP服务端通常在主循环中使用
recvfrom
和sendto
处理所有客户端的数据,不需要为每个客户端创建线程。 - (UDP服务端示例代码中没有使用线程,因为UDP是无连接的,通常不需要为每个客户端创建线程。如果需要处理大量并发UDP客户端,可以考虑使用线程池或非阻塞I/O。)
-
错误处理:
- TCP在建立连接时会进行错误处理,如连接失败会返回错误。
- UDP在发送和接收数据时可能会遇到错误,如目标不可达等,需要在发送和接收时处理。
-
资源消耗:
- TCP由于需要维护连接状态,资源消耗相对较高。
- UDP不需要维护连接状态,资源消耗相对较低。
-
适用场景:
- TCP适用于需要可靠传输的场景,如文件传输、网页浏览等。
- UDP适用于对实时性要求高的场景,如视频会议、在线游戏等。
2.服务端代码
#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")int main()
{WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);SOCKET udp_socket = socket(AF_INET, SOCK_DGRAM, 0);if (INVALID_SOCKET == udp_socket) {printf("create UDP socket failed !!! errocode: %d\n", GetLastError());return -1;}struct sockaddr_in local = { 0 };local.sin_family = AF_INET;local.sin_port = htons(8080);local.sin_addr.s_addr = inet_addr("0.0.0.0");if (-1 == bind(udp_socket, (struct sockaddr*)&local, sizeof(local))) {printf("bind socket failed !!! errocode: %d\n", GetLastError());return -1;}printf("UDP server is running\n");while (1) {char buffer[1024] = { 0 };struct sockaddr_in client_addr;int addr_len = sizeof(client_addr);int ret = recvfrom(udp_socket, buffer, 1024, 0, (struct sockaddr*)&client_addr, &addr_len);if (ret > 0) {printf("Received from %s:%d: %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);sendto(udp_socket, buffer, ret, 0, (struct sockaddr*)&client_addr, addr_len); // Echo back}}closesocket(udp_socket);WSACleanup();return 0;
}
3.客户端代码
#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")int main()
{WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);// 1. 创建socket套接字(通过套接字连接到服务端)SOCKET client_socket = socket(AF_INET, SOCK_DGRAM, 0);if (INVALID_SOCKET == client_socket) {printf("create socket failed!!!\n");return -1;}// 2. 定义服务器地址struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");// 3. 开始通讯 send recvwhile (1){char sbuffer[1024] = { 0 };printf("please enter: ");scanf("%s", sbuffer);// 发送数据到服务器sendto(client_socket, sbuffer, strlen(sbuffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));// 接收服务器回显char rbuffer[1024] = { 0 };int addr_len = sizeof(server_addr);int ret = recvfrom(client_socket, rbuffer, 1024, 0, (struct sockaddr*)&server_addr, &addr_len);if (ret <= 0) break;rbuffer[ret] = '\0'; // 添加字符串结束符printf("Received from server: %s\n", rbuffer);}// 4. 关闭socketclosesocket(client_socket);WSACleanup(); // 释放 Winsock 资源return 0;
}