基于Socket的UDP和TCP编程介绍

一、概述

1)TCP客户—服务器程序设计基本框架

TCP的三次握手与四次挥手(详解+动图)

  1. UDP客户—服务器程序设计基本框架流程图

  2. 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地址类型,数据传输方式,传输协议)
  1. 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(数据报套接字/无连接的套接字),我们已经在《套接字有哪些类型》一节中进行了介绍。

  1. 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()和connect()函数:绑定套接字并建立连接

服务器端侦听客户端的请求

对于服务器端程序,使用 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;
}


posted @ 2019-08-05 13:44  独孤剑—宇枫  阅读(1415)  评论(0编辑  收藏  举报