以前学C语言时学过,现在学C++时可以复习,重新巩固下基础知识。
什么是套接字:
TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口。套接字用(IP地址:端口号)表示。
套接字分类:
- 流式套接字(SOCK_STREAM):流式套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。
- 数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
- 原始套接字(SOCK_RAW):原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。
网络通信一般使用CS模式连接。
网络模型架构:
TCP/UDP属于传输层。
TCP网络通信:
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内 [1] 另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
通信流程图如下
TCP三次握手建立连接:
- 第一次握手:客户端发送syn包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
TCP四次握手断开连接:
- 第一次握手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当 然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接受数据。
- 第二次握手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
- 第三次握手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
- 第四次握手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
代码示例服务端(使用了select 模式,目前只可以进行一对一连接,如果想一对多的话,添加多线程即可)
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#define SERVER_PORT 6555
int main() {
fd_set rfds;
struct timeval tv;
int retval, maxfd; //选择器
/*创建socket*/
int ss = socket(AF_INET, SOCK_STREAM, 0); //AF_INET IPV4 ;SOCK_STREAM TCP
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(SERVER_PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*bind*/
if(bind(ss, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1) {
perror("bind");
exit(1);
}
/*listen*/
if(listen(ss, 5) == -1) {
perror("listen");
exit(1);
}
/*connect*/
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
///成功返回非负描述字,出错返回-1
int conn = accept(ss, (struct sockaddr*)&client_addr, &length); //目测需要客户端部分的addr
if( conn < 0 ) {
perror("connect");
exit(1);
}
while(1) {
/*把可读文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把标准输入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把当前连接的文件描述符加入到集合中*/
FD_SET(conn, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < conn)
maxfd = conn;
/*设置超时时间*/
tv.tv_sec = 5;
tv.tv_usec = 0;
/*等待聊天*/
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval == -1){
printf("select出错,客户端程序退出\n");
break;
}else if(retval == 0){
printf("服务端没有任何输入信息,并且客户端也没有信息到来,waiting...\n");
continue;
}else{
/*客户端发来了消息*/
if(FD_ISSET(conn,&rfds)){
char buffer[1024];
memset(buffer, 0 ,sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer), 0);
if(strcmp(buffer, "exit\n") == 0) break;
printf("%s", buffer);
//send(conn, buffer, len , 0);把数据回发给客户端
}
/*用户输入信息了,开始处理信息并发送*/
if(FD_ISSET(0, &rfds)){
char buf[1024];
fgets(buf, sizeof(buf), stdin);
//printf("you are send %s", buf);
send(conn, buf, sizeof(buf), 0);
}
}
}
close(conn);
close(ss);
return 0;
}
客户端:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MYPORT 6555
#define BUFFER_SIZE 1024
int main()
{
int sock_cli;
fd_set rfds;
struct timeval tv;
int retval, maxfd;
///定义sockfd
sock_cli = socket(AF_INET,SOCK_STREAM, 0);
///定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT); ///服务器端口
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ///服务器ip
//连接服务器,成功返回0,错误返回-1
while (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
while(1){
/*把可读文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把标准输入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把当前连接的文件描述符加入到集合中*/
FD_SET(sock_cli, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < sock_cli)
maxfd = sock_cli;
/*设置超时时间*/
tv.tv_sec = 5;
tv.tv_usec = 0;
/*等待聊天*/
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval == -1){
printf("select出错,客户端程序退出\n");
break;
}else if(retval == 0){
printf("客户端没有任何输入信息,并且服务器也没有信息到来,waiting...\n");
continue;
}else{
/*服务器发来了消息*/
if(FD_ISSET(sock_cli,&rfds)){
char recvbuf[BUFFER_SIZE];
int len;
len = recv(sock_cli, recvbuf, sizeof(recvbuf),0);
printf("%s", recvbuf);
memset(recvbuf, 0, sizeof(recvbuf));
}
/*用户输入信息了,开始处理信息并发送*/
if(FD_ISSET(0, &rfds)){
char sendbuf[BUFFER_SIZE];
fgets(sendbuf, sizeof(sendbuf), stdin);
send(sock_cli, sendbuf, strlen(sendbuf),0); //发送
memset(sendbuf, 0, sizeof(sendbuf));
}
}
}
close(sock_cli);
return 0;
}
UDP网络通信:
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。使用UDP可进行 单播 、广播 、组播。
UDP网络通信流程图:
示例代码服务端
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define SERV_PORT 8000
int main()
{
/* sock_fd --- socket文件描述符 创建udp套接字*/
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 将套接字和IP、端口绑定 */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(struct sockaddr_in)); //每个字节都用0填充
addr_serv.sin_family = AF_INET;//使用IPV4地址
addr_serv.sin_port = htons(SERV_PORT);//端口
/* INADDR_ANY表示不管是哪个网卡接收到数据,只要目的端口是SERV_PORT,就会被该应用程序接收到 */
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
len = sizeof(addr_serv);
/* 绑定socket */
if(bind(sock_fd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0)
{
perror("bind error:");
exit(1);
}
int recv_num;
int send_num;
char send_buf[20] = "i am server!";
char recv_buf[20];
struct sockaddr_in addr_client;
while(1)
{
printf("server wait:\n");
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_client, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("server receive %d bytes: %s\n", recv_num, recv_buf);
send_num = sendto(sock_fd, send_buf, recv_num, 0, (struct sockaddr *)&addr_client, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
}
close(sock_fd);
return 0;
}
客户端
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#define DEST_PORT 8000
#define DSET_IP_ADDRESS "127.0.0.1"
int main()
{
/* socket文件描述符 */
int sock_fd;
/* 建立udp socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 设置address */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);
addr_serv.sin_port = htons(DEST_PORT);
len = sizeof(addr_serv);
int send_num;
int recv_num;
char send_buf[20] = "hey, who are you?";
char recv_buf[20];
printf("client send: %s\n", send_buf);
send_num = sendto(sock_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&addr_serv, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("client receive %d bytes: %s\n", recv_num, recv_buf);
close(sock_fd);
return 0;
}
关于UDP广播组播相关示例待续...
参考: