以下的程序是基于阻塞模式下进行编写。没有采用多线程
套接字: 应用程序与网络的接口。
套接字相当于一个中介,绑定IP地址,端口号,采用TCP/UDP传输
以套接字为媒介,配合其他函数,来实现网络通信
套接字的功能: 监听、
发送数据、
接收数据、
监听过程: 服务器 等待客户端连接请求的到来
服务器的监听功能 在特定的 IP地址和端口上进行, 服务器的IP地址和端号必须是固定的 服务器要先启动
TCP协议和UDP协议的区别:
TCP: 双方必须建立连接(三次握手,安全可靠), 相当于打电话,
数据分段传输,可靠不会有丢失。
可以进行流量控制。
UDP: 双方无需同时在线,
不可靠传输,以数据包形式发送。
一方只管发送,至于发送成功不成功不管。
基于TCP和UDP的socket编程的区别:
1. 使用的套接字的类型不同,TCP使用的是流式套接字,UDP使用的是数据报式套接字
2. TCP是面向连接的,因此通信双方必须建立连接,连接部分需要我们通过编程实现
UDP是无连接的,对通信双方的状态无要求,你随时发你的数据,不需要告诉我什么时候发
3. TCP编程 相对于 UDP编程 多了: 监听、连接、接收连接部分;
SOCKET 结构体中:记录了 本地的IP 和 端口号,
记录了 目的地IP 和 端口号。
!!!!!!!!!!!!
想要通过套接字实现和对方的数据接收,该套接字结构体必须既保存了本机的IP和端口,也保存了目的地的IP 和 端口!!!!!!!!
基于TCP的网络应用程序编写(面向连接)
服务器端的程序一定要先启动
服务器端
综述: 需要通过两个套接字来实现与客户端的通信, 旧的套接字用来进行监听(是否有客户端进行连接请求)
新的套接字实现和客户端进行数据的接收和发送。
每一个客户端请求链接时,都会生成一个新的套接字
第1步: 加载套接字库
关键语句:WSAStartup(wVersionRequested, &wsaData)
第2步: 创建用于监听的流式套接字
关键语句: SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0)
// 该套接字结构体是空的,未记录本地 以及 目的地 的IP 和 端口
第3步: 将套接字与本地IP 和 端口 进行绑定
关键语句: bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
// 该套接字便记录下了本地IP 和 端口
// 绑定后功能:1. 通过套接字可以实现监听,即:客户端有没有请求连接
2.告诉客户端在哪个地址和端口等待数据的到来
注:基于TCP的这两个功能都用到了,基于UDP是只用到了第2个功能
第4步: 将套接字设置为监听模式
关键语句:listen(sockSrv, 5);
// 监听的目的是为连接做准备,只有监听到,才能进行连接
// 5 代表只准有5客户端等待连接,多余的将抛弃,可以根据自己的要求改动
第5步: 接收客户端的连接请求
关键语句: SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient,&len);
// 新的套接字应该 既记录了服务器 IP和端口,也记录了客户端的IP和端口
// 在没有客户端连接请求到来之前, 程序会一直阻塞在这个函数里。
// 用新的套接字来完成与客户端的接收、发送数据,原先的客户端仍然只是监听功能
// TCP协议三次握手建立可靠连接就发生在此步中
第6步: 用新的套接字与客户端进行数据的收发
关键语句: send(sockConn, sendbuf, 11, 0);
recv(sockConn, recvBuf1, 11, 0);
// 两个函数都是以字节来进行操作的。
// 进行数据的收发时,哪条语句放在前面无所谓
//因为TCP是有连接的,所以收发函数中没有 参数专门指向客户端的IP和端口
// 当没有数据过来时recv会一直等待,处于堵塞状态
第7步: 关闭新的套接字库
关键语句: closesocket(sockConn);
第8步: 关闭旧的套接库和终止对套接字库的使用(如果服务器不是死循环的时候)
关键语句: closesocket(sockSrv);
WSACleanup();
客户端
综述: 需要通过一个套接字来实现与客户端的通信
第1步: 加载套接字库
关键语句:WSAStartup(wVersionRequested, &wsaData)
第2步: 创建流式套接字
关键语句: SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0)
// 该套接字结构体是空的,未记录本地 以及 目的地 的IP 和 端口
第3步: 发出连接请求
关键语句: connect(sockSrv, (SOCKADDR*)&addrSrv, sizeof(addrSrv));
// addrSrv是服务器的IP 和 端口
// 该函数应该会把本地的IP 和 端口发送过去。
// TCP的三次握手发生在此时期
// 此时套接字记录下 服务器的IP 和端口 ,也会记录下本机客户端的IP 和 端口 ,
要不然为什么以后发送数据时只需要套接字就可以,而不需要再填写服务器的 IP 和 端口呢
第4步: 用套接字与客户端进行数据的收发
关键语句: send(sockConn, sendbuf, 11, 0);
recv(sockConn, recvBuf1, 11, 0);
// 两个函数都是以字节来进行操作的。
// 进行数据的收发时,哪条语句放在前面无所谓
//因为TCP是有连接的,所以收发函数中没有 参数专门指向客户端的IP和端口
// 当没有数据过来时recv会一直等待,处于堵塞状态
第5步: 关闭套接库和终止对套接字库的使用(如果客户端不是死循环的时候)
关键语句: closesocket(sockSrv);
WSACleanup();
基于UDP的网络应用程序编写(面向无连接)
UDP下服务器和客户端这种概念不是很强化,也就说其实无所谓谁是服务器端谁是客户端
服务器端
综述: 需要通过一个套接字来实现与客户端的通信, 套接字用来通信,不用来监听
第1步: 加载套接字库
关键语句:WSAStartup(wVersionRequested, &wsaData)
第2步: 创建数据报式套接字
关键语句: SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0)
// 该套接字结构体是空的,未记录本地 以及 目的地 的IP 和 端口
第3步: 将套接字与本地IP 和 端口 进行绑定
关键语句: bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
// 该套接字便记录下了本地IP 和 端口
// 绑定后功能:告诉客户端在哪个地址和端口等待数据的到来
第4步: 用套接字与客户端进行数据的收发
关键语句: recvfrom(sockSrv, recvBuf, 11, 0, (SOCKADDR *)&addrClient,&len);
sendto(sockSrv, sendbuf, strlen(sendbuf) + 1, 0, (SOCKADDR *)&addrClient, sizeof(SOCKADDR));
// 两个函数都是以字节来进行操作的。
// 进行数据的收发时,哪条语句放在前面无所谓
//因为UCP是有连接的,所以收发函数中必须有专门的参数保存着客户端的IP和端口
//此时的套接字我也不清楚有没有保存着客户端的IP 和端口,因为 再次发送数据的话仍然用的是此语句,仍有需要 IP和端口的参数
// 当没有数据过来时recvfrom会一直等待,处于堵塞状态
第5步: 关闭套接字库和终止对套接字库的使用(如果服务器不是死循环的时候)
关键语句: closesocket(sockSrv);
WSACleanup();
客户端
综述: 需要通过一个套接字来实现与客户端的通信
第1步: 加载套接字库
关键语句:WSAStartup(wVersionRequested, &wsaData)
第2步: 创建数据报式套接字
关键语句: SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0)
// 该套接字结构体是空的,未记录本地 以及 目的地 的IP 和 端口
第4步: 用套接字与客户端进行数据的收发
关键语句: recvfrom(sockSrv, recvBuf, 11, 0, (SOCKADDR *)&addrClient,&len);
sendto(sockSrv, sendbuf, strlen(sendbuf) + 1, 0, (SOCKADDR *)&addrClient, sizeof(SOCKADDR));
// 两个函数都是以字节来进行操作的。
// 进行数据的收发时,哪条语句放在前面无所谓
//因为UCP是有连接的,所以收发函数中必须有专门的参数保存着客户端的IP和端口
//此时的套接字我也不清楚有没有保存着客户端的IP 和端口,因为 再次发送数据的话仍然用的是此语句,仍有需要 IP和端口的参数
// 当没有数据过来时recvfrom会一直等待,处于堵塞状态
第5步: 关闭套接字库和终止对套接字库的使用(如果服务器不是死循环的时候)
关键语句: closesocket(sockSrv);
WSACleanup();
其他一些琐碎的小问题
recv和send函数都是按照字节进行接收的。
比如客户端中用send发送数据,则服务器中用recv接收消息,客户端中send()的数目和服务器中recv()的数目没有对应关系,原因:发送时用send发送了20个字节,用recv去接收的时候可能只接收了10个字节,
另外:服务器和客户端的收发可以同时进行,不用考虑谁先发送谁先接收