UC成长之路13

回顾:
UC成长之路1
UC成长之路2
UC成长之路3
UC成长之路4
UC成长之路5
UC成长之路6
UC成长之路7
UC成长之路8
UC成长之路9
UC成长之路10
UC成长之路11
UC成长之路12

一、基于TCP网络编程模型和实现

  • 传输层有两种:TCP和UDP

  • TCP面向连接的、可靠的、安全的但效率低

  • UDP面向包的、不可靠的但效率高

  • 建立TCP连接与断开:建立连接过程称为著名的三次握手,断开四次挥手
    在这里插入图片描述

  • 服务器端编程模型

	//1、创建一个套接字端点,返回一个文件描述符。lfd
	socket(2)
	//2、将lfd和服务器的ip地址和端口号绑定
	bind(2)
	//3、将lfd设置成被动连接模式,监听客户端连接的到来。有客户端连接的到来,放入未决连接队列中
	listen(2)
	//4、从未决连接队列中取出一个客户端连接,返回和客户端连接的文件描述符。使用这个文件描述符和客户端通讯。未决连接队列中没有数据,阻塞等待客户端的连接到来
	accept(2)
	while(1){
		//5从客户端获取数据
		read()
		//6、处理获取到的数据
		//7、将处理结果回送给客户端
		write()
		//8、关闭本次连接
		close()
	}
  • 客户端的编程模型
	//1、创建一个通讯端点,返回一个文件描述符
	socket(2)
	//2、使用这个文件描述符向服务器发起连接
	connect(2)
	//3、向服务器发送消息
	write()
	//4、等待服务器的相应消息
	read()
	//5、处理服务器的相应消息
	//6、关闭和服务器的连接,借宿通讯
	close()
  • socket(2)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//功能:创建一个通讯端点,返回一个文件描述符
//参数
//domain:
//1)AF_INET             IPv4 Internet protocols          ip(7)
//2)AF_INET6            IPv6 Internet protocols          ipv6(7)
//...
//type
//1)SOCK_STREAM:TCP
//2)SOCK_DGRAM:UDP
//...
//protocol:0
//返回值:成功返回一个用于新的socket的文件描述符;错误返回-1,errno被设置
  • bind(2)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//功能:给socket绑定一个名字
//参数:
//sockfd:指定了具体的socket(像手机的SIM卡插槽)
//addr:指定了服务器的地址和端口号(像SIM卡)
//addrlen:制定了addr的有效空间大小
//返回值:成功返回0;错误返回-1,error被设置

struct sockaddr {
      sa_family_t sa_family;
      char  sa_data[14];
};
  • listenl(2)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//功能:在指定socket上建立连接
//参数:
//sockfd:socket(2)的返回值
//backlog:指定了未决连接队列的最大长度
//返回值:成功返回0;错误返回-1,error被设置
  • accept(2)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//功能:在指定的socket上接收连接
//参数:
//sockfd:指定了socket(2)。socket(2)的返回值
//addr:如果addr是NULL,addrlen也要设置为空;
//addr指定的地址空间里填充客户端的地址家族的内容
//addrlen:值-结果参数,指定了addr的有效空间大小
//返回值:错误返回-1,errno被设置;成功返回一个非负整数,一个文件描述符,
//和客户端的连接描述符,使用这个连接描述符和客户端通讯。
  • connect(2)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//功能:在一个socket上发起一个连接,将socket连接到addr指向的地址上
//参数:
//sockfd:指定了socket
//addr:指定了地址
//addrlen:指定了addr空间的大小
//返回值:成功返回0;错误返回-1,errno被设置
  • IPV4家族、IPV6家族
  • 通用家族 struct sockaddr
struct sockaddr {
      sa_family_t sa_family;
      char  sa_data[14];
};
  • IPV4家族地址,使用man 7 ip查看
struct sockaddr_in {
       sa_family_t  sin_family; /* address family: AF_INET */
       in_port_t    sin_port;   /* port in network byte order */
       struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
        uint32_t   s_addr;     /* address in network byte order */
};
  • 配置端口号,配置ip地址
  • 网络字节序与主机字节序相互转换, htonl(3)
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
//h:host
//n:net
//s:short
//l:long
//to
  • ip地址的转化:字符串与无符号的长整型转换,inet_pton(3), inet_ntop(3)
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
//功能:text-->binary
//参数:
//af:AF_INET or AF_INET6
//src:字符串格式的ip,待转换的
//dst:存放转换后的结果
//返回值:成功返回1;af无效返回-1,errno被设置;src无效返回0

#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
              		char *dst, socklen_t size);
//功能:binary--->text
//参数:
//af:AF_INET or AF_INET6
//src:ip地址(binary)
//dst:转换后的ip地址(text)存放到这个地址指定的空间里
//size:指定dst缓冲区可用的字节数
//返回值:错误返回NULL,errno被设置;成功返回dst指向的地址,转换后的结果存放这个空间里       		

eg:编写基于TCP的服务器端和客户端程序

  • 服务器端负责将客户端发送过来的字符串转换为大写,server.c
  • 客户端负责向服务器发送字符串,然后将服务器转换后的字符串输出到显示器,client.c
    • server.c
#include <stdio.h>
#include <sys/types.h>     
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
    struct sockaddr_in serv;
    int cfd;//connect fd
    char buf[128];
    //創建1個socket,返回該socket的文件描述符ldf
    int lfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(lfd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化serv的成員
    serv.sin_family=AF_INET;
    serv.sin_port=htons(1024);
    //INADDR_ANY代表本機所有的ip地址
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //將lfd綁定到local的ip地址和端口
    int b = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1; 
    }   
    //將lfd設置成被動鏈接模式,監聽客戶端連接的到來。如果有客戶端的連接到來,將該連接放入未決連接隊列中
    listen(lfd, 5); 
    while(1){
        //從未決連接隊列中取出第一個連接進行處理,如果沒有未決連接,阻塞等待,有,返回一個文件描述符
        cfd = accept(lfd, NULL, NULL);
        if(cfd==-1){
            perror("accept");
            return -1; 
        }
        //這時候,三次握手已經完成,數據的處理
        //讀取客戶端的請求
        int r = read(cfd, buf, 128);
        int i;
        //處理客戶端的請求
        for(i=0; i<r; i++){
            buf[i]=toupper(buf[i]);//將字符轉換爲大寫
        }
        //響應客戶端,將處理信息響應給客戶端
        write(cfd, buf, r); 
        //關閉和客戶端的連接,結束這個連接
        close(cfd);
    }   
    return 0;
}
  • client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
    struct sockaddr_in serv;//服務器的ip地址和端口號
    char *msg = "this is a test...\n";
    char buf[128];
    //創建一個socket,返回該socket的文件描述副符
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd==-1){
        perror("socket");
        return -1;
    }
    //初始化服務器的ip地址和端口號
    serv.sin_family=AF_INET;//IPV4
    serv.sin_port=htons(1024);
    //服務器的ip地址,127.0.0.1,text-->binary
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr);
    //使用lfd向服務器發起連接,如果這個函數執行成功,三次握手成功
    int conn=connect(lfd, (struct sockaddr*)&serv, sizeof(serv));
    if(conn){
        perror("connect");
        return -1;
    }
    //向服務器發送字符串
    write(lfd, msg, strlen(msg));
    //等待服務器的響應信息,如果沒有響應阻塞等待
    int r=read(lfd, buf, 128);
    //將獲取到的響應信息輸出到顯示器
    write(1, buf, r);
    //關閉和服務器的連接,結束通訊
    close(lfd);
    return 0;
}

在这里插入图片描述

  • 改进eg
    server.c
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
    struct sockaddr_in serv, clie;
    int cfd;//connect fd
    char buf[128];
    //創建1個socket,返回該socket的文件描述符ldf
    int lfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(lfd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化serv的成員
    serv.sin_family=AF_INET;
    serv.sin_port=htons(1024);
    //INADDR_ANY代表本機所有的ip地址
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //將lfd綁定到local的ip地址和端口
    int b = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1; 
    }   
    //將lfd設置成被動鏈接模式,監聽客戶端連接的到來。如果有客戶端的連接到來,將該連接放入未決連接隊列中
    listen(lfd, 5); 
    while(1){
        socklen_t cli_len = sizeof(clie);
        //從未決連接隊列中取出第一個連接進行處理,如果沒有未決連接,阻塞等待,有,返回一個文件描述符
        cfd = accept(lfd, (struct sockaddr *)&clie, &cli_len);
        if(cfd==-1){
            perror("accept");
            return -1; 
        }
        char IP[64];
        //binary-->text
        printf("%s\n", inet_ntop(AF_INET, &clie.sin_addr, IP, 64));
        //這時候,三次握手已經完成,數據的處理
        //讀取客戶端的請求
        int r = read(cfd, buf, 128);
        int i;
        //處理客戶端的請求
        for(i=0; i<r; i++){
            buf[i]=toupper(buf[i]);//將字符轉換爲大寫
        }
        //響應客戶端,將處理信息響應給客戶端
        write(cfd, buf, r); 
        //關閉和客戶端的連接,結束這個連接
        close(cfd);
    }   
    return 0;
}

client.c

        perror("connect");
        return -1; 
    }   
    //向服務器發送字符串
    write(lfd, msg, strlen(msg));
    //等待服務器的響應信息,如果沒有響應阻塞等待
    int r=read(lfd, buf, 128);
    //將獲取到的響應信息輸出到顯示器
    write(1, buf, r); 
    //關閉和服務器的連接,結束通訊
    close(lfd);
    return 0;
}

在这里插入图片描述

  • 封装eg
    t_net.h
#ifndef T_NET_H_
#define T_NET_H_

/*include file*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*類型的聲明*/
typedef struct sockaddr_in SA4;
typedef struct sockaddr SA; 

/*函數的聲明,創建socket,將返回的描述符綁定到本地地址*/
int socket_b(int port);
int trans(int fd);//客戶端的業務處理

#endif

t_net.c

#include <stdio.h>
#include "t_net.h"

int socket_b(int port)
{
    SA4 serv;
    //創建1個socket,返回該socket的文件描述符ldf
    int lfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(lfd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化serv的成員
    serv.sin_family=AF_INET;
    serv.sin_port=htons(port);
    //INADDR_ANY代表本機所有的ip地址
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //將lfd綁定到local的ip地址和端口
    int b = bind(lfd, (SA *)&serv, sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1; 
    }   
    return lfd;
}

//業務的處理
int trans(int fd) 
{
    char buf[128];
    //讀取客戶端的請求
    int r=read(fd,buf,128);
    int i;
    //處理客戶端的請求
    for(i=0;i<r;i++){
        buf[i]=toupper(buf[i]);//將字符轉換爲大寫
    }   
    write(fd, buf, r); 
    return 0;
}

serverp.c

#include <stdio.h>
#include <unistd.h>
#include "t_net.h"

int main(void)
{
    SA4 clie;
    int cfd;//connect fd
    int lfd=socket_b(1024);
    if(lfd==-1) return -1; 
    //將lfd設置成被動鏈接模式,監聽客戶端連接的到來。如果有客戶端的連接到來,將該連接放入未決連接隊列中
    listen(lfd, 5); 
    while(1){
        socklen_t cli_len = sizeof(clie);
        //從未決連接隊列中取出第一個連接進行處理,如果沒有未決連接,阻塞等待,有,返回一個文件描述符
        cfd = accept(lfd, (SA *)&clie, &cli_len);
        if(cfd==-1){
            perror("accept");
            return -1; 
        }
        char IP[64];
        //binary-->text
        printf("%s\n", inet_ntop(AF_INET, &clie.sin_addr, IP, 64));
        //這時候,三次握手已經完成,數據的處理
        trans(cfd);//業務的處理
        //關閉和客戶端的連接,結束這個連接
        close(cfd);
    }   
    return 0;
}

client.c

    inet_pton(AF_INET, argv[1], &serv.sin_addr);
    //使用lfd向服務器發起連接,如果這個函數執行成功,三次握手成功
    int conn=connect(lfd, (SA *)&serv, sizeof(serv));
    if(conn==-1){
        perror("connect");
        return -1; 
    }   
    //向服務器發送字符串
    write(lfd, msg, strlen(msg));
    //等待服務器的響應信息,如果沒有響應阻塞等待
    int r=read(lfd, buf, 128);
    //將獲取到的響應信息輸出到顯示器
    write(1, buf, r); 
    //關閉和服務器的連接,結束通訊
    close(lfd);
    return 0;
}
  • eg:客户端和服务器一次连接,传输多个字符串。如果客户端要结束这次连接,就输入exit。
    t_net.h
#ifndef T_NET_H_
#define T_NET_H_

/*include file*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*類型的聲明*/
typedef struct sockaddr_in SA4;
typedef struct sockaddr SA; 

/*函數的聲明,創建socket,將返回的描述符綁定到本地地址*/
int socket_b(int port);
int trans(int fd);//客戶端的業務處理

#endif

t_net.c

#include <stdio.h>
#include <string.h>
#include "t_net.h"

int socket_b(int port)
{
    SA4 serv;
    //創建1個socket,返回該socket的文件描述符ldf
    int lfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(lfd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化serv的成員
    serv.sin_family=AF_INET;
    serv.sin_port=htons(port);
    //INADDR_ANY代表本機所有的ip地址
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //將lfd綁定到local的ip地址和端口
    int b = bind(lfd, (SA *)&serv, sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1; 
    }   
    return lfd;
}

//業務的處理
int trans(int fd) 
{
    char buf[128];
    while(1){
        memset(buf, 0, 128);
        //讀取客戶端的請求
        int r=read(fd,buf,128);
        int i;
        //處理客戶端的請求
        for(i=0;i<r;i++){
            buf[i]=toupper(buf[i]);//將字符轉換爲大寫
        }
        write(fd, buf, r); 
        if(strcmp(buf,"EXIT")==0) break;
    }   
    return 0;
}

serverp.c

#include <stdio.h>
#include <unistd.h>
#include "t_net.h"

int main(void)
{
    SA4 clie;
    int cfd;//connect fd
    int lfd=socket_b(1024);
    if(lfd==-1) return -1; 
    //將lfd設置成被動鏈接模式,監聽客戶端連接的到來。如果有客戶端的連接到來,將該連接放入未決連接隊列中
    listen(lfd, 5); 
    while(1){
        socklen_t cli_len = sizeof(clie);
        //從未決連接隊列中取出第一個連接進行處理,如果沒有未決連接,阻塞等待,有,返回一個文件描述符
        cfd = accept(lfd, (SA *)&clie, &cli_len);
        if(cfd==-1){
            perror("accept");
            return -1; 
        }
        char IP[64];
        //binary-->text
        printf("%s\n", inet_ntop(AF_INET, &clie.sin_addr, IP, 64));
        //這時候,三次握手已經完成,數據的處理
        trans(cfd);//業務的處理
        //關閉和客戶端的連接,結束這個連接
        close(cfd);
    }   
    return 0;
}

clientp.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "t_net.h"

int main(int argc, char* argv[])
{
    SA4 serv;//服務器的ip地址和端口號
    char buf[128];
    char msg[128];
    //創建一個socket,返回該socket的文件描述副符
    int lfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(lfd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化服務器的ip地址和端口號
    serv.sin_family=AF_INET;//IPV4
    serv.sin_port=htons(1024);
    //服務器的ip地址,127.0.0.1,text-->binary
    inet_pton(AF_INET, argv[1], &serv.sin_addr);
    //使用lfd向服務器發起連接,如果這個函數執行成功,三次握手成功
    int conn=connect(lfd, (SA *)&serv, sizeof(serv));
    if(conn==-1){
        perror("connect");
        return -1; 
    }   
    while(gets(msg)){
    
        //向服務器發送字符串
        write(lfd, msg, strlen(msg));
        memset(buf, 0, 128);
        //等待服務器的響應信息,如果沒有響應阻塞等待
        int r=read(lfd, buf, 128);
        if(strcmp(buf,"EXIT")==0) break;
        //將獲取到的響應信息輸出到顯示器
        write(1, buf, r); 
        printf("\n");
    }   
    //關閉和服務器的連接,結束通訊
    close(lfd);
    return 0;
}

在这里插入图片描述

二、并发服务器的实现

  • 并发的实现:多路复用、线程、进程
  • 父进程
    • 从未决连接队列中取一个连接
    • 创建子进程
    • 关闭客户端的连接描述符
    • 回收子进程的资源,waitpid()
  • 子进程
    • close(lfd)
    • 处理客户端的具体业务
    • close(cfd)
    • exit(0)

eg:并发服务器的代码
t_net.h

#ifndef T_NET_H_
#define T_NET_H_

/*include file*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*類型的聲明*/
typedef struct sockaddr_in SA4;
typedef struct sockaddr SA; 

/*函數的聲明,創建socket,將返回的描述符綁定到本地地址*/
int socket_b(int port);
int trans(int fd);//客戶端的業務處理

#endif

t_net.c

#include <stdio.h>
#include <string.h>
#include "t_net.h"

int socket_b(int port)
{
    SA4 serv;
    //創建1個socket,返回該socket的文件描述符ldf
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd==-1){
        perror("socket");
        return -1;
    }
    //初始化serv的成員
    serv.sin_family=AF_INET;
    serv.sin_port=htons(port);
    //INADDR_ANY代表本機所有的ip地址
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //將lfd綁定到local的ip地址和端口
    int b = bind(lfd, (SA *)&serv, sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1;
    }
    return lfd;
}

//業務的處理
int trans(int fd)
{
    char buf[128];
    while(1){
        memset(buf, 0, 128);
        //讀取客戶端的請求
        int r=read(fd,buf,128);
        int i;
        //處理客戶端的請求
        for(i=0;i<r;i++){
            buf[i]=toupper(buf[i]);//將字符轉換爲大寫
        }
        write(fd, buf, r);
        if(strcmp(buf,"EXIT")==0) break;
    }
    return 0;
}

servers.c

#include <stdio.h>
#include <unistd.h>
#include "t_net.h"
#include <stdlib.h>
#include <sys/wait.h>

int main(void)
{
    SA4 clie;
    int cfd;//connect fd
    int lfd=socket_b(1024);
    if(lfd==-1) return -1; 
    //將lfd設置成被動鏈接模式,監聽客戶端連接的到來。如果有客戶端的連接到來,將該連接放入未決連接隊列中
    listen(lfd, 5); 
    while(1){
        socklen_t cli_len = sizeof(clie);
        //從未決連接隊列中取出第一個連接進行處理,如果沒有未決連接,阻塞等待,有,返回一個文件描述符
        cfd = accept(lfd, (SA *)&clie, &cli_len);
        if(cfd==-1){
            perror("accept");
            return -1; 
        }
        char IP[64];
        //binary-->text
        printf("%s\n", inet_ntop(AF_INET, &clie.sin_addr, IP, 64));
        //創建子進程
        pid_t pid=fork();
        if(pid==-1){
            perror("fork");
            return -1; 
        }
        if(pid==0){//子進程的任務
            close(lfd);
            //這時候,三次握手已經完成,數據的處理
            trans(cfd);//業務的處理
            //關閉和客戶端的連接,結束這個連接
            close(cfd);
            exit(0);
        }
        else{//父進程的任務
            close(cfd);
            //非阻塞
            waitpid(-1, NULL, WNOHANG);
        }
    }   
    return 0;
}

clientp.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "t_net.h"

int main(int argc, char* argv[])
{
    SA4 serv;//服務器的ip地址和端口號
    char buf[128];
    char msg[128];
    //創建一個socket,返回該socket的文件描述副符
    int lfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(lfd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化服務器的ip地址和端口號
    serv.sin_family=AF_INET;//IPV4
    serv.sin_port=htons(1024);
    //服務器的ip地址,127.0.0.1,text-->binary
    inet_pton(AF_INET, argv[1], &serv.sin_addr);
    //使用lfd向服務器發起連接,如果這個函數執行成功,三次握手成功
    int conn=connect(lfd, (SA *)&serv, sizeof(serv));
    if(conn==-1){
        perror("connect");
        return -1; 
    }   
    while(gets(msg)){
    
        //向服務器發送字符串
        write(lfd, msg, strlen(msg));
        memset(buf, 0, 128);
        //等待服務器的響應信息,如果沒有響應阻塞等待
        int r=read(lfd, buf, 128);
        if(strcmp(buf,"EXIT")==0) break;
        //將獲取到的響應信息輸出到顯示器
        write(1, buf, r); 
        printf("\n");
    }   
    //關閉和服務器的連接,結束通訊
    close(lfd);
    return 0;
}

在这里插入图片描述

补充:setsockopt(2)解决ip地址重用问题(地址重用是因为传输层以下需要在内核态实现,刚结束使用的地址资源还没被回收,马上重用该地址就会发生地址重用问题)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

三、基于UDP的编程模型及其实现

  • 使用UDP进行网络通讯,根本不需要建立连接
  • 模型
//服务端:
//1.创建一个socket通讯端点
//2.将这个socket绑定到本地地址
while(1){
	 //3.使用recvfrom阻塞等待客户端数据的到来
	 //4.处理从客户端获取的数据
	 //5.将结果响应给客户端
} 

//客户端:
//1.创建一个socket
//2.使用这个socket向服务器发送消息
//3.阻塞等待服务器的响应消息
//4.处理响应消息
//5.关闭socket,结束客户端的进程
  • sendto(2) \ recvfrom(2)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);
//功能:在socket上发送一条消息
//参数
//sockfd:指定了socket,在这个socket上发送消息
//buf:指定了存储要发送的数据的地址
//len:要发送的数据的长度
//flags:0(还有其他的可以看man帮助)
//dest_addr:指定了目标地址
//addrlen:指定了dest_addr指向的地址空间大小
//返回值:成功返回发送出去的字节数;错误返回-1,errno被设置

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
//功能:从socket上接收一条消息
//参数
//sockfd:指定了socket,在这个socket上接收消息
//buf:指定了地址,用这个地址开始空间存储消息
//len:指定了buf空间的大小
//flags:0(还有其他的可以看man帮助)
//src_addr:指定的地址空间里填充客户端的地址家族的内容
//addrlen:值-结果参数,指定了addr的有效空间大小
//返回值:成功返回接收的字节数;错误返回-1

eg:编程实现UDP通讯的服务端和客户端

userver.c

#include <stdio.h>
#include "t_net.h"
#include <ctype.h>

int main(void)
{
    SA4 serv, clie;
    char buf[128];
    socklen_t cli_len;
    //創建基於udp的socket
    int fd=socket(AF_INET, SOCK_DGRAM, 0); 
    if(fd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化服務器的ip地址和端口號
    serv.sin_family=AF_INET;
    serv.sin_port=htons(4000);
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    //將fd綁定到本地的ip地址和端口號
    int b=bind(fd, (SA *)&serv, sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1; 
    }   
    while(1){
        cli_len=sizeof(SA4);
        //從客戶端獲取數據
        int rcv=recvfrom(fd, buf, 128, 0, (SA *)&clie, &cli_len);
        if(rcv==-1){
            perror("recvfrom");
            return -1; 
        }
        int i;
        for(i=0; i<rcv; i++){
            buf[i]=toupper(buf[i]);    
        }
        //將處理結果發送給客戶端
        int s=sendto(fd, buf, rcv, 0, (SA *)&clie, sizeof(SA4));
        if(s==-1){
            perror("sendto");
            return -1; 
        }
    }   
    return 0;
}

uclient.c

#include <stdio.h>
#include "t_net.h"
#include <string.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    char *msg="this is a test...\n";
    SA4 serv;
    char buf[128];
    //創建一個socket,基於udp的
    int fd=socket(AF_INET, SOCK_DGRAM, 0); 
    if(fd==-1){
        perror("socket");
        return -1; 
    }   
    //初始化服務器的ip地址和端口號
    serv.sin_family=AF_INET;
    serv.sin_port=htons(4000);
    inet_pton(AF_INET, argv[1], &serv.sin_addr);
    //向服務器發送消息
    int s=sendto(fd,msg,strlen(msg),0,(SA *)&serv,sizeof(SA4));
    if(s==-1){
        perror("sendto");
        return -1; 
    }   
    //阻塞等待服務器的響應消息
    int rcv=recvfrom(fd,buf,128,0,NULL,NULL);
    if(rcv==-1){
        perror("recvfrom");
        return -1; 
    }   
    //將服務器的響應消息輸出到顯示器
    write(1, buf, rcv);
    return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值