基于Socket的UDP和TCP编程介绍
一、概述
1)TCP客户—服务器程序设计基本框架
TCP的三次握手与四次挥手(详解+动图)
-
UDP客户—服务器程序设计基本框架流程图
-
UDP和TCP的对比:
从上面的流程图比较我们可以很明显的看出UDP没有三次握手过程。
简单点说。UDP处理的细节比TCP少。UDP不能保证消息被传送到(它也报告消息没有传送到)目的地。UDP也不保证数据包的传送顺序。UDP把数据发出去后只能希望它能够抵达目的地。
TCP优缺点:
优点:
1.TCP提供以认可的方式显式地创建和终止连接。
2.TCP保证可靠的、顺序的(数据包以发送的顺序接收)以及不会重复的数据传输。
3.TCP处理流控制。
4.允许数据优先
5.如果数据没有传送到,则TCP套接口返回一个出错状态条件。
6.TCP通过保持连续并将数据块分成更小的分片来处理大数据块。—无需程序员知道
缺点: TCP在转移数据时必须创建(并保持)一个连接。这个连接给通信进程增加了开销,让它比UDP速度要慢。
UDP优缺点:
1.UDP不要求保持一个连接
2.UDP没有因接收方认可收到数据包(或者当数据包没有正确抵达而自动重传)而带来的开销。
3.设计UDP的目的是用于短应用和控制消息
4.在一个数据包连接一个数据包的基础上,UDP要求的网络带宽比TDP更小。
二、Socket编程
Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。
Socket接口设计者先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
1、socket调用库函数主要有:
创建套接字
int Socket(int af,int type,int protocol) --------------> socket(ip地址类型,数据传输方式,传输协议)
- af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
127.0.0.1,它是一个特殊IP地址,表示本机地址,后面的教程会经常用到。
你也可以使用 PF 前缀,PF 是“Protocol Family”的简写,它和 AF 是一样的。例如,PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6。
2) type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字),我们已经在《套接字有哪些类型》一节中进行了介绍。
- protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
建立地址和套接字的联系 (服务器端要用 bind() 函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理)
int bind(int sock, struct sockaddr *addr, socklen_t addrlen) -------------->bind(套接字标识符,结构体指针,结构体变量的大小)
sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
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); //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
服务器端侦听客户端的请求
对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。
listen( Sockid ,quenlen) --------------> listen( 套接字标识符 ,请求队列的长度)
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
建立服务器/客户端的连接 (面向连接TCP)
客户端请求连接
Connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen) -------------->Connect(套接字标识符,结构体指针,结构体变量的大小)
当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
newsockid=accept(Sockid,Clientaddr, paddrlen) --------------> accept(套接字标识符,客户端的结构体指针变量, 结构体变量长度)
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
发送/接收数据
面向连接:send(sockid, buff, bufflen)
recv( )
面向无连接:sendto(sockid,buff,…,addrlen)
recvfrom( )
send()/recv()和write()/read():发送数据和接收数据
释放套接字
close(sockid)
基于socket的服务端与客户端之间的相互通信:
服务端的代码:
/* socket_tcp_server.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h> ///errno的头文件,不能用errno.h
#define MAX_MSG_SIZE 256
int main(int argc, char const *argv[])
{
/* code */
int server_socket_fd;//服务端套接字标识符
int client_socket_fd;
struct sockaddr_in server_addr;//服务端的信息
struct sockaddr_in client_addr;//客户端的信息
char msg[MAX_MSG_SIZE] = {0};//消息缓冲区
char sendmsg[MAX_MSG_SIZE] = {0};//发送数据
/* 创建套节字 */
server_socket_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (server_socket_fd < 0) {
fprintf(stderr,"create socket ERROR:%s\n",strerror(errno));
exit(1);
}
/* 将套接字和IP、端口绑定 */
memset(&server_addr, 0, sizeof(server_addr)); //每个字节都用0初始化
server_addr.sin_family = AF_INET;//使用ipv4地址
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
server_addr.sin_port = htons(4321); //端口
bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
/* 创建套接字监听队列 */
if (listen(server_socket_fd,10) <0 ) {//监听队列最大的长度为10
fprintf(stderr,"listen ERROR:%s\n",strerror(errno));
close(server_socket_fd);
exit(1);
}
socklen_t client_addr_size = sizeof(client_addr);//客户端信息的长度
/* 接受客户请求 */
client_socket_fd = accept(server_socket_fd,(struct sockaddr*)&client_addr,&client_addr_size);
if (client_socket_fd <= 0) {
fprintf(stderr,"accept ERROR:%s\n",strerror(errno));
}else {
while (1)
{
/* 接收到客户端的请求后,服务端开始为客户端服务 */
/* 传数据到客户端 */
//strcpy(msg,"你好啊,客户端老弟,我是服务端渣渣辉啊!!");
printf("请输入你要传递给客户端的信息:\n");
gets(sendmsg);
//write(client_socket_fd,sendmsg,MAX_MSG_SIZE);
send(client_socket_fd,sendmsg,MAX_MSG_SIZE,0);
memset(sendmsg,0,MAX_MSG_SIZE);
printf("我是服务端小贱贱:正在为您服务ing!!!!\n");
//int read_count = read(client_socket_fd,msg,MAX_MSG_SIZE);
int read_count = recv(client_socket_fd,msg,MAX_MSG_SIZE,0);
if (read_count > 0) {
printf("received a connection from %s\n", inet_ntoa(client_addr.sin_addr));
printf("the message is :%s from the client\n",msg);
memset(msg,0,MAX_MSG_SIZE);
}
}
}
/* 关闭套接字 */
close(client_socket_fd);
close(server_socket_fd);
return 0;
}
客户端的代码:
/* socket_tcp_client.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DATA_MAX_SIZE 256
int main(int argc, char const *argv[])
{
/* code */
int client_socket_fd;
int result;
struct sockaddr_in server_addr;//服务端的信息
/* 创建套节字 */
client_socket_fd = socket(AF_INET,SOCK_STREAM,0);
/* 将套接字和IP、端口绑定 */
memset(&server_addr, 0, sizeof(server_addr)); //每个字节都用0初始化
server_addr.sin_family = AF_INET;//使用ipv4地址
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
server_addr.sin_port = htons(4321); //端口
result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(result == -1)
{
perror("ops:client\n");
exit(1);
}
/* 读取服务器数据 */
char data[DATA_MAX_SIZE] = {0};//接受数据
char sendmsg[DATA_MAX_SIZE] = {0};//发送数据
while (1)
{
/* 接受数据 */
//int read_count = read(client_socket_fd,data,DATA_MAX_SIZE);
int read_count = recv(client_socket_fd,data,DATA_MAX_SIZE,0);
if (read_count > 0 ) {
printf("the message is :%s from the server\n",data);
}
/* 发送数据 */
printf("请输入你要发给服务端的数据:\n");
gets(sendmsg);
//sendmsg[DATA_MAX_SIZE-1] = '\0';
//write(client_socket_fd,sendmsg,DATA_MAX_SIZE);
send(client_socket_fd,sendmsg,DATA_MAX_SIZE,0);
}
/* 关闭套接字 */
close(client_socket_fd);
return 0;
}