UDP通信
2019-05-29 16:14:59 fanle93 阅读数 832
  • TCP/IP/UDP Socket通讯开发实战 适合iOS/Android/...

    本课程适合中学员,适用于从事iOS/Android/嵌入式Linux网络通讯开发的学员。实战案例可用于无人机,安防,直播等。从Linux音频,视频采集,到TCP/IP UDP Socket基础概念,网络编程接口介绍,POSIX线程封装,私有协议定义,开发,服务器模型,客户端编程等详细实战讲解,整个过程,涵盖iOS,Android ,Mac OS嵌入式Linux网络编程核心的大量实用场景。让学员能够掌握相关知识,融汇贯通掌握网络通讯开发核心知识。 付费学员加入QQ群,可获得本人未来1~3年学习过程中的专业指导解答。第三节课第7分15秒有QQ群,欢迎付费学员加入探讨技术问题。

    14728 人正在学习 去看看 陈超

UDP简介

UDP(User Datagram Protocol)即用户数据报协议,是OSI(Open System Interconnection,即开放式系统互联)参考模型中的一种传输层协议。
OSI参考模型
该协议提供了一种面向无连接模式的通信。由于这种特性,使用UDP协议进行传输时的开销更小,但同时并不能保证被传输的数据能够到达目的地。

UDP协议特性

总结一下UDP协议的一些特性:

  • 需要资源少
  • 不保证接收
  • 无连接

UDP协议与TCP协议的主要区别

TCP协议简介

TCP(Transmission Control Protocol)即传输控制协议,与UDP协议一样,是OSI参考模型中的一种传输层协议。

TCP协议特性

为了明晰UDP协议与TCP协议两者之间的区别,有必要对TCP的一些特性进行简要的了解。

  • 面向有连接模式
    在建立通信的时候需要三次握手,同时在断开通信的时候需要四次挥手
  • 顺序传输与累计确认
    每个数据包均有一个ID,按照ID顺序发送,同时为了保证不丢包,需要对发送的包进行应答,称为累计确认。
  • 确认与重传机制
    如果数据包没有收到,会要求发送端重发
  • 流量控制
    控制发送速度让接收端来得及接收
  • 拥塞控制
    在不堵塞的情况下尽量发挥带宽

主要区别

UDP TCP
面向无连接 面向有连接
支持一对一、一对多、多对一、和多对多的通信 只能有两个端点,实现一对一的通信
不保证数据传输的可靠性 传输数据无差错,不丢失,不重复,且按时序到达
占用资源较少 占用资源较多

UDP协议应用场景

基于以上特性,UDP协议更适合应用于:

  • 系统资源较少的嵌入式系统
  • 网络条件稳定的内部局域网
  • IP与端口号都固定的情况
  • 对实时性要求较高的情况

UDP通信代码

本文提供了两个操作系统下,UDP通信的代码。

Linux系统下的UDP通信代码

发送端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

//第一个传参,目的ip地址
//第二个传参,目的端口号

int main(int argc, char *argv[])
{
	//输出参数数量校验
	if (argc < 3)
	{
		printf("输入的参数数量错误!\n");
		printf(“请检查并重新执行此程序!\n");
		return -1;
	}
	
	char buf[1000] = { "hello world" };									//声明发送数据缓存区
	struct sockaddr_in sockaddr_dest;									//声明接收服务器地址
	
	int fd = socket(AF_INET, SOCK_DGRAM, 0);							//建立socket
																		//AF_INET		IPV4
																		//SOCK_DGRAM	UDP
																		//0				传输层通信

	//socket建立测试
	if (client_fd < 0)
	{
		printf("socket建立失败!\n");
		return -1;
	}
	
	//清空接收服务器地址
	memset(&client_addr, 0, sizeof(sockaddr_dest));
	
	//配置接收端服务器
	sockaddr_dest.sin_family = AF_INET;									//ipv4
	sockaddr_dest.sin_addr.s_addr = inet_addr(argv[1]);					//第一个传参,ip地址
	sockaddr_dest.sin_port = htons(atoi(argv[2]));						//第二个传参,端口号
	
	//发送数据
	sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sockaddr_dest, sizeof(sockaddr_dest));
	
	//关闭socket
	close(fd);
}

接收端(阻塞模式)

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

//第一个传参,本机ip地址
//第二个传参,本机端口号

int main(int argc, char *argv[])
{
	//输出参数数量校验
	if (argc < 3)
	{
		printf("输入的参数数量错误!\n");
		printf(“请检查并重新执行此程序!\n");
		return -1;
	}
	
	int len = sizeof(struct sockaddr_in);
	char buf[100] = { 0 };

	struct sockaddr_in sockaddr_recv;									//接收端通信地址结构
	struct sockaddr_in sockaddr_send;									//发送端通信地址结构
	
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	
	sockaddr_recv.sin_family = AF_INET;
	sockaddr_recv.sin_port = htons(33333);
	sockaddr_recv.sin_addr.s_addr =  htonl(INADDR_ANY);

	//绑定socket
	int ret = bind(fd, (struct sockaddr *)&sockaddr_recv, sizeof(sockaddr_recv));
	
	//判断是否绑定成功
	if (ret == 0)
	{
		printf("绑定成功!\n");
	}
	else
	{
		printf("绑定失败!\n");
	}
	
	//以阻塞端方式接收数据
	recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sockaddr_send, &len);

	//打印接收到的数据
	printf("recv is %s\n", buf);
}

接收端(非阻塞模式)

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <unistd.h>

//第一个传参,本机ip地址
//第二个传参,本机端口号
//第三个船餐,等待时间,单位秒

int main(int argc, char *argv[])
{
	//输出参数数量校验
	if (argc < 4)
	{
		printf("输入的参数数量错误!\n");
		printf(“请检查并重新执行此程序!\n");
		return -1;
	}
	
	int len = sizeof(struct sockaddr_in);
	char buf[100] = { 0 };

	struct sockaddr_in sockaddr_recv;									//接收端通信地址结构
	struct sockaddr_in sockaddr_send;									//发送端通信地址结构
	
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	
	sockaddr_recv.sin_family = AF_INET;
	sockaddr_recv.sin_port = htons(33333);
	sockaddr_recv.sin_addr.s_addr =  htonl(INADDR_ANY);

	//绑定socket
	int ret = bind(fd, (struct sockaddr *)&sockaddr_recv, sizeof(sockaddr_recv));
	
	//判断是否绑定成功
	if (ret == 0)
	{
		printf("绑定成功!\n");
	}
	else
	{
		printf("绑定失败!\n");
	}
	
	//使用select完成非阻塞
	fd_read read;														//声明一个fd_set集合来保存被检测的句柄
	struct timeval timeout;												//声明一个时间结构来保存阻塞的时间
	
	//循环接收
	while(1)
	{
		FD_ZERO(&read);													//使用select函数之前先将集合清零
		FD_SET(fd, &read);												//将所要检测端socket句柄加入到集合中
		timeout.tv_sec = atoi(argv[3]);		           			     	//设置select等待的最大时间
		timeout.tv_usec = 0;
		
		int ret_select = select(4, &read, NULL, NULL, &timeout);		//检测集合read中的句柄是否有可读信息
		
		//如果返回值小于0,则select函数出错
		if (ret_select < 0)
		{
			printf("select函数出错!\n");
			return -1;
		}
		//如果返回值为0,则在计时周期内socket的状态没有发生改变
		else if (ret_select == 0)
		{
			printf("此周期内无数据接收!\n");
			sleep(1);													//无数据接收,等待1秒
		}
		//ret_select记录了发生变化的句柄的个数
		else
		{
			if (FD_ISSET(fd, &read))									//如果这个被监视端句柄真的变为可读了
			{
				memset(buf, 0, 100);									//先将接收缓存区清零
				len = sizeof(sockaddr_send);							//获取发送端通信地址结构长
				
				//在这一时间周期内以阻塞的方式接收数据
				int recv = recvfrom(server_fd, buf, BUF_LEN, 0, (struct sockaddr *)&sockaddr_send, &len);
				//判断是否接收到了数据
				if (recv == -1)
				{
					printf("接收数据失败!\n");
					return -1;
				}
				printf("recv is %s\n", buf);
			}
		}
	}
}

Windows系统下的UDP通信代码

配置UDP

int Deploy_UDP(int &skt, char *IP_Address, UINT port)
{
	skt = socket(AF_INET, SOCK_DGRAM, 0);
	struct sockaddr_in svrsockaddr;
	svrsockaddr.sin_family = AF_INET;
	svrsockaddr.sin_port = htons(port);
	svrsockaddr.sin_addr.S_un.S_addr = inet_addr(IP_Address);
	int result_bind = bind(skt, (struct sockaddr FAR*)&svrsockaddr, sizeof(struct sockaddr_in));
	//设置UDP的模式为非阻塞接收
	u_long unblock=100;
	int result_unblock = ioctlsocket(skt, FIONBIO, &unblock);
	return 0;
}

发送函数

int shareSend(int skt, char *buffer, int len, char *IP_Address, UINT port)
{
	sockaddr_in Comsockaddr;
	Comsockaddr.sin_family = AF_INET;
	Comsockaddr.sin_port = htons(port);
	Comsockaddr.sin_addr.S_un.S_addr = inet_addr(IP_Address);
	return sendto(skt, buffer, len, 0, (struct sockaddr FAR*)&Comsockaddr, sizeof(sockaddr_in));
}

接收函数

int UDPRecv(int skt, BYTE *buffer, int len, struct sockaddr_in &recvsock)
{
	int len_recvsock = sizeof(struct sockaddr_in);
	if (skt > 0)
	{
		return recvfrom(skt, (char *)buffer, len, 0, (struct sockaddr FAR*)&recvsock, &len_recvsock);
	}
	return -1;
}

关闭函数

int UDPClose(int skt)
{
	closesocket(skt);
	return 0;
}

2019-06-02 16:09:06 boke_fengwei 阅读数 361
  • TCP/IP/UDP Socket通讯开发实战 适合iOS/Android/...

    本课程适合中学员,适用于从事iOS/Android/嵌入式Linux网络通讯开发的学员。实战案例可用于无人机,安防,直播等。从Linux音频,视频采集,到TCP/IP UDP Socket基础概念,网络编程接口介绍,POSIX线程封装,私有协议定义,开发,服务器模型,客户端编程等详细实战讲解,整个过程,涵盖iOS,Android ,Mac OS嵌入式Linux网络编程核心的大量实用场景。让学员能够掌握相关知识,融汇贯通掌握网络通讯开发核心知识。 付费学员加入QQ群,可获得本人未来1~3年学习过程中的专业指导解答。第三节课第7分15秒有QQ群,欢迎付费学员加入探讨技术问题。

    14728 人正在学习 去看看 陈超

udp连接特性

  • 无连接:可以不构成连接就进行通信
  • 不可靠:数据并不能保证可靠性
  • 面向数据报:每条数据有长度限制,整条数据发送整条数据接受,传输不灵活,但是不会存在粘包问题。
    原理在网络版块讲解
    udp通信流程
    在这里插入图片描述

c++封装udp接口,封装接口便于我们更好的实现

  1 /*
  2  *udp的封装接口
  3  */
  4 #include <iostream>
  5 #include <string>
  6 #include <stdio.h>
  7 #include <unistd.h>
  8 #include <sys/socket.h>
  9 #include <errno.h>
 10 #include <stdlib.h>
 11 #include <netinet/in.h>
 12 #include <arpa/inet.h>
 13 
 14 class UdpSocket{
 15 public:
 16     UdpSocket():_socket(-1){
 17     };
 18     ~UdpSocket(){
 19     };
 20     //创建socket
 21     bool Socket(){
 22         _socket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 23         if(_socket < 0){
 26         }
 27         return true;
 28     }
 29     //绑定连接
 30     bool Bind(std::string ip,uint16_t port){
 31         struct sockaddr_in addr;
 32         addr.sin_family = AF_INET;
 33         addr.sin_port = htons(port);
 34         addr.sin_addr.s_addr = inet_addr(ip.c_str());
 35 
 36         int len = sizeof(addr);
 37 
 38         int ret = bind(_socket,(struct sockaddr*)&addr,len);
 39         if(ret < 0){
 40             perror("bind error\n");
 41             return false;
 42         }
 43         return true;
 44     }
 45     //接受数据
 46     bool Recv(std::string &buf,struct sockaddr_in *saddr){
 47         //*daddr是发送的ip信息
 48         //  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
 49         //  struct sockaddr *src_addr, socklen_t *addrlen);
 50         char temp[1500];
 51         socklen_t len = sizeof(struct sockaddr_in);
 53         if(ret < 0){
 54             perror("recvfrom error\n");
 55             return false;
 56         }
 57         buf.assign(temp,ret);
 58         return true;
 59     }
 60     //发送数据
 61     bool Send(std::string& str,struct sockaddr_in* daddr){
 62     //  ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
 63     //  const struct sockaddr *dest_addr, socklen_t addrlen);
 64         int len = sizeof(struct sockaddr_in);
 65         int ret = sendto(_socket,str.c_str(),str.size(),0 
 66                 ,(struct sockaddr*)daddr,len);
 67         if(ret < 0){
 68             perror("sendto error\n");
 69             return false;
 70         }
 71         return true;
 72     }
 73     //关闭连接
 74     bool Close(){
 75         close(_socket);
 76         _socket = -1;
 77     }
 78     private:
 79     int  _socket;
 80 }; 

udp服务端的实现

4 #include "udpsocket.hpp"
  5 #define CHECK(T) if(!T) {return -1;}
  6 int main(int argc,char* argv[]){
  1 /*
  2  *实现udp服务器端
  3  */
  4 #include "udpsocket.hpp"
  5 #define CHECK(T) if(!T) {return -1;}
  6 int main(int argc,char* argv[]){
  7     //将我们的ip地址通过参数进行传递
  8     if(argc < 3){
  9         perror("./a.out ip port\n");
 10         return -1;
 11     }
 12     std::string ip = argv[1];//ip地址的信息
 13     uint16_t port = atoi(argv[2]);//port端口信息
 14     //创建封装的类
 15     UdpSocket sock;
 16     //创建我们的套接字
 17     CHECK(sock.Socket());
 18     //绑定
 19     CHECK(sock.Bind(ip,port));
 20     while(1){
 21         //接受数据
 22         std::string str;
 23         struct sockaddr_in client_addr;
 24         CHECK(sock.Recv(str,&client_addr));
 25         std::cout<<"client said:"<<str<<std::endl;
 26         std::string answer;
 27         std::cout<<"server say:";
 28         fflush(stdout);
 29         std::cin>>answer;
 30         CHECK(sock.Send(answer,&client_addr));
 31     }
 32     sock.Close();
 33     return 0;
 34 }  

udp客户端代码实现

  1 /*
  2  udp客户端程序实现
  3  */
  4 #include "udpsocket.hpp"
  5 
  6 #define CHECK(T) if(!T) {return -1;}
  7 int main(int argc,char* argv[]){
  8     if(argc < 3){
  9         perror("./a.out ip port\n");
  1 /*
  2  udp客户端程序实现
  3  */
  4 #include "udpsocket.hpp"
  5 
  6 #define CHECK(T) if(!T) {return -1;}
  7 int main(int argc,char* argv[]){
  8     if(argc < 3){
  9         perror("./a.out ip port\n");
 10         return -1;
 11     }
 12     std::string ip = argv[1];
 13     uint16_t port = atoi(argv[2]);
 14 
 15     UdpSocket sock;
 16     //创建套接字
 17     CHECK(sock.Socket());
 18     //客户端不用创建连接,绑定的是绑定的服务器端的地址信息
 19     struct sockaddr_in ser_addr;
 20     ser_addr.sin_family = AF_INET;
 21     ser_addr.sin_port = htons(port);
 22     ser_addr.sin_addr.s_addr = inet_addr(ip.c_str());
 23     
 24     while(1){
 25         //发送数据
 26         std::string str;
 27         std::cout<<"client say:";
 28         fflush(stdout);
 29         std::cin>>str;
 30         CHECK(sock.Send(str,&ser_addr));
 31         std::string answer;
 32         CHECK(sock.Recv(answer,&ser_addr));
 33         std::cout<<"server said:"<<answer<<std::endl;
 34     }
 35     sock.Close();
 36     return 0;
 37 }  
2016-11-29 00:08:51 qq_25077833 阅读数 1203
  • TCP/IP/UDP Socket通讯开发实战 适合iOS/Android/...

    本课程适合中学员,适用于从事iOS/Android/嵌入式Linux网络通讯开发的学员。实战案例可用于无人机,安防,直播等。从Linux音频,视频采集,到TCP/IP UDP Socket基础概念,网络编程接口介绍,POSIX线程封装,私有协议定义,开发,服务器模型,客户端编程等详细实战讲解,整个过程,涵盖iOS,Android ,Mac OS嵌入式Linux网络编程核心的大量实用场景。让学员能够掌握相关知识,融汇贯通掌握网络通讯开发核心知识。 付费学员加入QQ群,可获得本人未来1~3年学习过程中的专业指导解答。第三节课第7分15秒有QQ群,欢迎付费学员加入探讨技术问题。

    14728 人正在学习 去看看 陈超

LINUX下一个简单的基于UDP的验证系统,主要还是在于对UDP套接字及SOCKET编程的一些基础使用,使用TLV结构封装数据包来发送验证的账号密码,同时对账号密码使用MD5进行加密,代码中还是存在较多的小问题的,但是对网络编程的一个较完整的网络编程实例。

整个程序分为5个文件:
1. client_fns.h :客户端头文件
2. client.c:客户端实现
3. server_fns.h:服务端头文件
4. server.c:服务端实现
5. md5.h:MD5加密的实现

1. client_fns.h


/* 所需头文件包含 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <termios.h>
#include <unistd.h>
#include <assert.h>
#include <netdb.h>  /* netbd.h is needed for struct hostent */
#include <memory.h> /* for MD5 */
#include "md5.h"    /* for MD5 */

/* 宏定义 */
#define PORT 1234           /* 欲访问服务端的端口号 */
#define IP "192.168.0.115"  /* 欲访问服务端的IP地址*/
#define MAX_BUF_SIZE 54     /* 缓冲区的最大长度 */
#define MAX_ACCOUNT_SIZE 16 /* 账号密码最大长度*/

/* 指定数据类型 */
typedef char INT8;
typedef short INT16;
typedef int INT32;
typedef unsigned char UCHAR;
typedef signed char CHAR;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef signed long long INT64;
typedef unsigned long long UINT64;
typedef int BOOL;

/* 全局变量 */
INT8 gPktId = 0; /* 存储报文id */

/* 请求报文结构体 */
struct request
{
    INT8 code;
    INT8 id;
    INT16 requestLength;
    INT8 auth[16];
    INT8 tlvType1;
    INT8 tlvLength1;
    UCHAR tlvValue1[MAX_ACCOUNT_SIZE];
    INT8 tlvType2;
    INT8 tlvLength2;
    UCHAR tlvValue2[MAX_ACCOUNT_SIZE];
};

/* 回应报文结构体 */
struct reply
{
    INT8 code;
    INT8 id;
    INT16 replyLength;
    INT8 auth[16];
    INT8 tlvType1;
    INT8 tlvLength1;
    INT8 tlvValue1;
};

/****************************************
* DESCRIPTION:
*     回复报文解析
* INPUTS:
*     pkt - 回复报文指针,输入为将用于存储报文信息的结构体
*     str - 字符型数组,输入为接收缓冲区
* OUTPUTS:
*     none
* RETURNS:
*     none
****************************************/
void parseReplyPacket(struct reply *pkt, INT8 *str)
{
    /* 解析code */
    pkt->code = str[0];

    /* 解析id */
    pkt->id = str[1];

    /* 解析requestLength */
    memcpy(&pkt->replyLength, &str[2], 2);

    /* 解析auth[16] */
    memcpy(&pkt->auth, &str[4], 16);

    /* 解析tlvType1 */
    pkt->tlvType1 = str[20];

    /* 解析tlvLength1 */
    pkt->tlvLength1 = str[21];

    /* 解析tlvValue1[MAX_ACCOUNT_SIZE] */
    pkt->tlvValue1 = str[22];
    return;
}

/****************************************
* DESCRIPTION:
*     在Lunux下模拟实现 getch 函数
* INPUTS:
*     none
* OUTPUTS:
*     none
* RETURNS:
*     c - 字符的ASCII码
****************************************/
INT32 getch(void)
{
    INT32 c = 0;
    struct termios org_opts, new_opts;
    INT32 res = 0;

    /* store old settings */
    res = tcgetattr(STDIN_FILENO, &org_opts);
    assert(res == 0);

    /* set new terminal parms */
    memcpy(&new_opts, &org_opts, sizeof(new_opts));
    new_opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOKE | ICRNL);
    tcsetattr(STDIN_FILENO, TCSANOW, &new_opts);
    c = getchar();

    /* restore old settings */
    res = tcsetattr(STDIN_FILENO, TCSANOW, &org_opts); assert(res == 0);
    if (c != '\n')
        putchar('*');
    return c;
}

/****************************************
*DESCRIPTION:
*    对用户名和密码进行合法性检查
*INPUTS:
*    str - 字符数组指针,输入为账号字符串密码字符串
*OUTPUTS:
*    none
*RETURNS:
*    1 - 合法性验证不通过
*    0 - 合法性验证通过
****************************************/
INT8 inputLegitimacy(INT8 * str)
{
    /* 参数定义 */
    INT8 length = strlen(str); /* 定义字符长度 */
    INT8 *pStr = str + 1;      /* 定义字符指针 */

    /* 判断输入数据的长度 */
    if (length < 6 || length > 12)
    {
        printf("\n\n\033[22;31mError 1 : The Length Of Your Username Or Password Must In 6 To 12 Codes\n\033[0m");
        return 0;
    }

    /* 判断首字符是否为字母 */
    if (!isalpha(*str)) /* 是字符的话,isalpha函数返回1 */
    {
        printf("\n\n\033[22;31mError 2 : The First Code Of Your Username Or Password Must Be Alphabet Or Number\n\033[0m");
        return 0;
    }

    /* 判断字符是否只包含字母和数字 */
    while ((*pStr) != '\0')
    {
        if ((!isalpha(*pStr)) && (!(((*pStr) >= '0') && ((*pStr) <= '9'))))
        {
            printf("\n\n\033[22;31mError 3 : The Codes Of Your Username Or Password Only Include Alphabet Or Number\n\033[0m");
            return 0;
        }
        pStr++;
    }
    return 1;
}

/****************************************
*DESCRIPTION:
*    将数据封装好并至发送至缓冲区
*INPUTS:
*    str - 字符数组指针,输入为空的发送缓冲区
*OUTPUTS:
*    str - 字符数组指针,将封装好的报文发送至发送缓冲区
*RETURNS:
*    none
****************************************/
void bulidRequestPacket(INT8 *str)
{
    /* 参数定义 */
    INT8 i = 0;
    UCHAR ch;
    UCHAR Usrname[30];
    UCHAR UsrPwd[30];
    struct request requestPkt;

    /* 进行账号、密码的输入及合法性检查 */
    do
    {
        memset(UsrPwd, '\0', 30);
        i = 0;
        printf("\033[22;32m\n====== Please input your username ====== \n\033[0m");
        gets(Usrname);
        printf("\033[22;32m\n====== Please input your password ====== \n\033[0m");
        while ((ch = getch()) != '\n')
        {
            UsrPwd[i] = ch;
            i++;
        }
        UsrPwd[i] = '\0';
    } while (inputLegitimacy(Usrname) == 0 || inputLegitimacy(UsrPwd) == 0);

    /* 对密码进行md5加密 */
    UCHAR decrypt[16];
    MD5_CTX md5;
    MD5Init(&md5);
    MD5Update(&md5, UsrPwd, strlen((INT8 *)UsrPwd));
    MD5Final(&md5, decrypt); /* 加密后的结果存在decrypt数组中 */

    /* 封装code */
    requestPkt.code = 1;

    /* 封装id */
    requestPkt.id = gPktId;
    gPktId++; /* 包id加1 */

    /* 封装requestLength */
    requestPkt.requestLength = 24 + strlen(Usrname) + 16;

    /* 封装校验 */
    memset(&requestPkt.auth, 0x01, 16);

    /* 封装tlvType1 */
    requestPkt.tlvType1 = 1;

    /* 封装tlvLength1 */
    requestPkt.tlvLength1 = strlen(Usrname) + 2;

    /* 封装tlvValue1 */
    memcpy(&requestPkt.tlvValue1, Usrname, strlen(Usrname));

    /* 封装tlvType2 */
    requestPkt.tlvType2 = 2;

    /* 封装tlvLength2 */
    requestPkt.tlvLength2 = 16 + 2;

    /* 封装tlvValue2 */
    for (i = 0; i < 16; i++)
    {
        requestPkt.tlvValue2[i] = decrypt[i];
    }

    /*拷贝报文内容封装至包里*/
    memcpy(str, &requestPkt, 22 + strlen(Usrname));
    memcpy(str + 22 + strlen(Usrname), &requestPkt.tlvType2, 2 + 16);
    return;
}

/****************************************
* DESCRIPTION:
*     打印回复报文
* INPUTS:
*     PKT - 回复报文结构体,输入为欲打印的回复报文
* OUTPUTS:
*     none
* RETURNS:
*     none
****************************************/
void printReplyPkt(struct reply PKT)
{
    INT32 i = 0;
    printf("\ncode=%d\n", PKT.code);
    printf("id=%d\n", PKT.id);
    printf("rLength=%d\n", PKT.replyLength);
    printf("auth=");
    for (i = 0; i < 16; i++)
    {
        printf("%x", PKT.auth[i]);
    }
    printf("\n");
    printf("tlvType1=%d\n", PKT.tlvType1);
    printf("tlvLength1=%d\n", (int)PKT.tlvLength1);
    printf("tlvValue1=%d", PKT.tlvValue1);
    printf("\n\n");
}

/* MD5算法 */
unsigned char PADDING[] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

void MD5Init(MD5_CTX *context)
{
    context->count[0] = 0;
    context->count[1] = 0;
    context->state[0] = 0x67452301;
    context->state[1] = 0xEFCDAB89;
    context->state[2] = 0x98BADCFE;
    context->state[3] = 0x10325476;
}
void MD5Update(MD5_CTX *context, unsigned char *input, UINT32 inputlen)
{
    UINT32 i = 0, index = 0, partlen = 0;
    index = (context->count[0] >> 3) & 0x3F;
    partlen = 64 - index;
    context->count[0] += inputlen << 3;
    if (context->count[0] < (inputlen << 3))
        context->count[1]++;
    context->count[1] += inputlen >> 29;

    if (inputlen >= partlen)
    {
        memcpy(&context->buffer[index], input, partlen);
        MD5Transform(context->state, context->buffer);
        for (i = partlen; i + 64 <= inputlen; i += 64)
            MD5Transform(context->state, &input[i]);
        index = 0;
    }
    else
    {
        i = 0;
    }
    memcpy(&context->buffer[index], &input[i], inputlen - i);
}
void MD5Final(MD5_CTX *context, unsigned char digest[16])
{
    UINT32 index = 0, padlen = 0;
    unsigned char bits[8];
    index = (context->count[0] >> 3) & 0x3F;
    padlen = (index < 56) ? (56 - index) : (120 - index);
    MD5Encode(bits, context->count, 8);
    MD5Update(context, PADDING, padlen);
    MD5Update(context, bits, 8);
    MD5Encode(digest, context->state, 16);
}
void MD5Encode(unsigned char *output, UINT32 *input, UINT32 len)
{
    UINT32 i = 0, j = 0;
    while (j < len)
    {
        output[j] = input[i] & 0xFF;
        output[j + 1] = (input[i] >> 8) & 0xFF;
        output[j + 2] = (input[i] >> 16) & 0xFF;
        output[j + 3] = (input[i] >> 24) & 0xFF;
        i++;
        j += 4;
    }
}
void MD5Decode(UINT32 *output, unsigned char *input, UINT32 len)
{
    UINT32 i = 0, j = 0;
    while (j < len)
    {
        output[i] = (input[j]) |
            (input[j + 1] << 8) |
            (input[j + 2] << 16) |
            (input[j + 3] << 24);
        i++;
        j += 4;
    }
}
void MD5Transform(UINT32 state[4], unsigned char block[64])
{
    UINT32 a = state[0];
    UINT32 b = state[1];
    UINT32 c = state[2];
    UINT32 d = state[3];
    UINT32 x[64];
    MD5Decode(x, block, 64);
    FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */
    FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */
    FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */
    FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */
    FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */
    FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */
    FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */
    FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */
    FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */
    FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */
    FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
    FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
    FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */
    FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
    FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
    FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */

    /* Round 2 */
    GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */
    GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */
    GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
    GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */
    GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */
    GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */
    GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
    GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */
    GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */
    GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */
    GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */
    GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */
    GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */
    GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */
    GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */
    GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */

    /* Round 3 */
    HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */
    HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */
    HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
    HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
    HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */
    HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */
    HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */
    HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
    HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */
    HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */
    HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */
    HH(b, c, d, a, x[6], 23, 0x4881d05); /* 44 */
    HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */
    HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
    HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
    HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */

    /* Round 4 */
    II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */
    II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */
    II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
    II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */
    II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */
    II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */
    II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
    II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */
    II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */
    II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
    II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */
    II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
    II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */
    II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
    II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */
    II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */
    state[0] += a;
    state[1] += b;
    state[2] += c;
    state[3] += d;
}

2. client.c


#include "client_fns.h" /* 包含所有所需所有头文件及函数定义 */

INT32 main()
{
    /* 参数定义 */
    INT32 sockfd = 0;                           /* 定义文件描述符 */
    INT32 numbytes = 0;                         /* 定义成功收到数据的字节数 */
    INT32 i = 0;                                /* 循环参数 */
    UCHAR recvbuf[MAX_BUF_SIZE];                /* 接收缓冲区 */
    UCHAR sendbuf[MAX_BUF_SIZE];                /* 发送缓冲区 */
    memset(recvbuf, '\0', MAX_BUF_SIZE);        /* 接受缓冲区初始化 */
    memset(sendbuf, '\0', MAX_BUF_SIZE);        /* 发送缓冲区初始化 */
    socklen_t len = sizeof(struct sockaddr_in); /* 地址结构长度 */

    /* 地址信息存储结构体定义 */
    struct hostent *he;             /* 用于获取服务端主机信息 */
    struct sockaddr_in server;      /* 保存服务器主机地址信息 */
    struct sockaddr_in client;      /* 保存客户端主机地址信息 */
    struct reply PKT;               /* 保存服务端发送的回应报文信息*/
    bzero(&server, sizeof(server)); /* 初始化server */
    bzero(&client, sizeof(client)); /* 初始化client */

    /* 根据输入获取主机信息并判断是否正确 */
    if ((he = gethostbyname(IP)) == NULL)
    {
        printf("gethostbyname() error\n");
        exit(1);
    }

    /* 创建套接字并判断是否正确 */
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        printf("socket() error\n");
        exit(1);
    }

    /* 初始化服务端信息 */
    server.sin_family = AF_INET;                       /* 初始化服务端使用IPV4*/
    server.sin_port = htons(PORT);                     /* 初始化服务端主机端口 */
    server.sin_addr = *((struct in_addr *)he->h_addr); /* 初始化服务端IP地址,从he获取 */

    /* 循环进行发送与接收 */
    while (1)
    {
        /******************************************************************
        * 报文发送
        ******************************************************************/
        memset(sendbuf, '\0', MAX_BUF_SIZE); /* 发送缓冲区初始化 */
        bulidRequestPacket(sendbuf); /* 报文封装 */
        sendto(sockfd, sendbuf, MAX_BUF_SIZE, 0, (struct sockaddr *)&server, len); /* 报文发送 */

        /******************************************************************
        * 报文接收
        ******************************************************************/
        memset(recvbuf, '\0', MAX_BUF_SIZE);/* 接受缓冲区初始化 */
        if ((numbytes = recvfrom(sockfd, recvbuf, MAX_BUF_SIZE, 0, (struct sockaddr *)&server, &len)) == -1){ /* 报文接收与检验 */
            printf("recvfrom() error\n");
            exit(1);
        }
        recvbuf[numbytes] = '\0'; /* 设置最后一位 */
        parseReplyPacket(&PKT, recvbuf); /* 回复报文解析 */
        if (PKT.tlvValue1 == 0) /* 判断回复结果,0为错误,1为普通用户,2为管理员 */
        {
            printf("\033[22;31m\n\n- Wrong Account Or Password Please Input Again\n\n\033[0m");
        }
        else{
            if (PKT.tlvValue1 == 1)
            {
                printf("\033[22;32m\n\n- Welcome General User\n\n\033[0m");
            }
            else{
                printf("\033[22;32m\n\n- Welcome Administrator\n\n\033[0m");
            }
        }
        /* printReplyPkt(PKT); */

    }

    /* 关闭套接字 */
    close(sockfd);
    return 0;
}

3. server_fns.h


/* 头文件包含 */
#include <stdio.h>
#include <string.h>
#include <unistd.h> 
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>  
#include <netdb.h>  
#include <fcntl.h> 
#include <time.h>

/* 宏定义 */
#define PORT 1234       /* 将开放的端口号 */
#define MAX_BUF_SIZE 54 /* 缓冲区的最大长度 */
#define MAX_ACCOUNT_SIZE 17      /* 账号密码最大长度*/

/* 指定数据类型 */
typedef char INT8;
typedef short INT16;
typedef int INT32;
typedef unsigned char UCHAR;
typedef signed char CHAR;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef signed long long INT64;
typedef unsigned long long UINT64;
typedef int BOOL;

/* 请求报文结构体 */
struct request
{
    INT8 code;
    INT8 id;
    INT16 requestLength;
    INT8 auth[16];
    INT8 tlvType1;
    INT8 tlvLength1;
    UCHAR tlvValue1[MAX_ACCOUNT_SIZE];
    INT8 tlvType2;
    INT8 tlvLength2;
    UCHAR tlvValue2[MAX_ACCOUNT_SIZE];
};


/* 回应报文结构体 */
struct reply
{
    INT8 code;
    INT8 id;
    INT16 replyLength;
    INT8 auth[16];
    INT8 tlvType1;
    INT8 tlvLength1;
    INT8 tlvValue1;
};

/****************************************
* DESCRIPTION:
*     对客户端发来的报文进行解析,将解析结果存入本地报文结构体中
* INPUTS:
*     pkt - 请求报文指针,输入为一个空的报文指针
*     str - 报文数据流数组,输入为客户端所发送的报文流数组
* OUTPUTS:
*     pkt - 解析报文流数组,将对报文的解析信息存储在结构体中
* RETURNS:
*     none
****************************************/
void parseRequestPacket(struct request *pkt, char* str)
{
    /* 参数定义 */
    INT32 offset = 0; /* 用于记录报文数据段的偏移量*/

    /* 解析code */
    offset = 0;
    pkt->code = str[offset];

    /* 解析id */
    offset = offset + 1;
    pkt->id = str[offset];

    /* 解析requestLength */
    offset = offset + 1;
    memcpy(&pkt->requestLength, &str[offset], 2);

    /* 解析auth[16] */
    offset = offset + 2;
    memcpy(&pkt->auth, &str[offset], 16);

    /* 解析tlvType1 */
    offset = offset + 16;
    pkt->tlvType1 = str[offset];

    /* 解析tlvLength1 */
    offset = offset + 1;
    pkt->tlvLength1 = str[offset];

    /* 解析tlvValue1[MAX_ACCOUNT_SIZE] */
    offset = offset + 1;
    memcpy(&pkt->tlvValue1, &str[offset], (INT32)pkt->tlvLength1 - 2);
    pkt->tlvValue1[(INT32)pkt->tlvLength1 - 2] = '\0';

    /* 解析tlvType2 */
    offset = offset + (INT32)pkt->tlvLength1 - 2;
    pkt->tlvType2 = str[offset];

    /* 解析tlvLength2 */
    offset = offset + 1;
    pkt->tlvLength2 = str[offset];

    /* 解析tlvValue2[MAX_ACCOUNT_SIZE] */
    offset = offset + 1;
    memcpy(&pkt->tlvValue2, &str[offset], (INT32)pkt->tlvLength2 - 2);
    pkt->tlvValue2[(INT32)pkt->tlvLength2 - 2] = '\0';
    return;
}

/****************************************
* DESCRIPTION:
*     进行用户帐号、密码的验证
* INPUTS:
*     usr - 用户账号指针,输入为用户账号数组
*     pwd - 用户密码指针,输入为用户密码数组
* OUTPUTS:
*     none
* RETURNS:
*     0 - 表示用户账号、密码错误
*     1 - 表示验证为普通用户
*     2 - 表示验证为管理员
****************************************/
INT32 accountCheck(INT8 * usr, INT8 *pwd)
{
    /* 参数定义 */
    FILE * fp;
    INT8 ch;
    INT32 i;       /* 循环变量 */
    INT32 flag;    /* 判断读取状态 */
    INT32 temp;    /* 临时存储变量 */
    INT8 name[17]; /* 临时存储账号 */
    INT8 pass[17]; /* 临时存储密码 */
    INT8 quanxian; /* 临时存储权限 */
    i = 0;         /* 初始化参数 */
    flag = 0;      /* 初始化参数 */

    /* 打开文件 */
    if ((fp = fopen("./data", "r")) == NULL)
    {
        fprintf(stderr, "Can't open data\n");
        exit(1);
    }

    /* 读取文件并进行账号密码的验证 */
    while ((ch = fgetc(fp)) != EOF)
    {
        if ((INT32)ch == 10)/* 判断换行 LF == 10 */
        {
            switch (flag)
            {
            case 0:/* 已完成账号读取 */
                name[i] = '\0';
                i = 0;
                break;
            case 1:/* 已完成密码读取*/
                pass[i] = '\0';
                i = 0;
                break;
            case 2:/* 已完成权限读取,进行账号、密码的验证 */
                if ((strcmp(name, usr) == 0) && (strcmp(pass, pwd) == 0))
                {
                    fclose(fp);
                    return quanxian - '0'; /* 返回权限等级 */
                }
                break;
            default:
                break;
            }
            flag = (flag + 1) % 3; /* 初始化读取状态 */
            continue;
        }
        switch (flag)
        {
        case 0:/* 进行账号读取 */
            name[i] = ch;
            i++;
            break;
        case 1:/* 进行密码读取 */
            pass[i] = ch;
            i++;
            break;
        case 2:/* 进行权限读取 */
            quanxian = ch;
            break;
        default:
            break;
        }
    }
    /* 关闭文件 */
    fclose(fp);
    return 0;
}

/****************************************
* DESCRIPTION:
*     回应报文的封装,也就输出缓存区的赋值
* INPUTS:
*     str - 缓冲区指针,输入为输出缓冲区
*     check - 账户整形验证结果,输入为用户密码验证结果
*     packetId - 请求报文的整形id,输入为请求报文的id字段
* OUTPUTS:
*     str - 缓冲区指针,输出为赋值好的缓冲区
* RETURNS:
*     none
****************************************/
void bulidReplyPacket(char *str, INT32 check, INT32 packetId)
{
    struct reply replyPkt; /* 回应报文结构体*/

    /* 封装code */
    if (check == 0) /* 验证结果为0则为错误,其他为请求通过 */
    {
        replyPkt.code = 3; /* 拒绝请求 */
    }
    else
    {
        replyPkt.code = 2; /* 通过认证 */
    }

    /* 封装id */
    replyPkt.id = packetId;

    /* 封装replyLength */
    replyPkt.replyLength = 23;

    /* 封装校验 */
    memset(&replyPkt.auth, 0x01, 16);

    /* 封装tlvType1 */
    replyPkt.tlvType1 = 3;/* 3为权限TLV */

    /* 封装tlvLength1 */
    replyPkt.tlvLength1 = 3;

    /* 封装tlvValue1 */
    replyPkt.tlvValue1 = check;

    /* 拷贝报文内容封装至包里 */
    memcpy(str, &replyPkt, 23);
    return;
}

/****************************************
* DESCRIPTION:
*     线程运行主函数,报文的接收与发送
* INPUTS:
*     arg - 文件描述参数
* OUTPUTS:
*     none
* RETURNS:
*     none
****************************************/
void *thread(void *arg)
{
    /* 定义参数 */
    INT32 sockfd = 0;                            /* 文件描述符 */
    INT32 msgNum = 0;                            /* 存储缓冲区长度 */
    INT32 i = 0;                                 /* 循环参数 */
    INT32 temp = 0;                              /* 临时变量存储 */
    INT32 identifyResult = 0;                    /* 账号验证结果 */
    UCHAR recvmsg[MAX_BUF_SIZE];                 /* 接收缓冲区 */
    UCHAR sendmsg[24];                           /* 发送缓冲区 */
    socklen_t len = sizeof(struct sockaddr_in);  /* 地址结构长度 */
    sockfd = *((INT32*)arg);                     /* 初始化文件描述符 */
    time_t timep;                                /* 定义时间参数 */
    time(&timep);                                /* 获取当前时间 */

    /* 输出提示字符串设置 */
    INT8 time[] = "Request Time : ";
    INT8 ip[] = "Request Ip : ";
    INT8 errorAccount[] = "Error account or password";
    INT8 generalUser[] = "General User";
    INT8 administrator[] = "Administrator";
    INT8 account[] = "\nRequest Account : ";
    INT8 identify[] = "\nRequest Identify Result : ";
    INT8 doubleLf[] = "\n\n";
    //INT32 quit[] = 'quit';
    /* 定义地址信息存储结构体 */
    struct sockaddr_in server; /* 保存服务器主机地址信息 */
    struct sockaddr_in client; /* 保存客户端主机地址信息 */
    struct request PKT;        /* 保存客户端发送的请求报文信息*/

    /******************************************************************
    * 报文接收
    ******************************************************************/
    memset(recvmsg, '\0', MAX_BUF_SIZE); /* 初始化接受缓冲区 */
    msgNum = recvfrom(sockfd, recvmsg, MAX_BUF_SIZE, 0, (struct sockaddr *)&client, &len); /* 接收报文并将长度赋值给msgNum */
    if (msgNum < 0) /* 判断接收报文是否出错 */
    {
        perror("recvfrom error\n");
        exit(1);
    }
    parseRequestPacket(&PKT, recvmsg); /* 解析请求报文,并讲结果存入PKT结构体中*/
    for (i = 0; i < 16; i++) /* MD5编码转换 */
    {
        temp = (INT32)PKT.tlvValue2[i] % 25 + 97;
        PKT.tlvValue2[i] = (INT8)temp;
    }
    identifyResult = accountCheck(PKT.tlvValue1, PKT.tlvValue2); /* 进行账号验证,并存储结果 */

    /* 进行验证结果的日志输出 */
    FILE  *fp = fopen("./log", "a+");        /* 定义日志文件指针 */
    fprintf(fp, time);                       /* 提示输出日期 */
    fprintf(fp, asctime(gmtime(&timep)));    /* 日期输出 */
    fprintf(fp, ip);                         /* 提示输出ip */
    fprintf(fp, inet_ntoa(client.sin_addr)); /* 输出ip */
    fprintf(fp, account);                    /* 提示输出账号 */
    fprintf(fp, PKT.tlvValue1);              /* 输出账号*/
    fprintf(fp, identify);                   /* 提示输出验证结果 */
    switch (identifyResult)                  /* 输出验证结果,进行匹配判断 */
    {
    case 0: /* 错误的信息 */
        fprintf(fp, errorAccount);
        break;
    case 1: /* 普通用户 */
        fprintf(fp, generalUser);
        break;
    case 2: /* 管理员 */
        fprintf(fp, administrator);
        break;
    default:
        break;
    }
    fprintf(fp, doubleLf); /* 输出换行 */
    fclose(fp);            /* 关闭文件 */

    /* 终端请求提示显示 */
    printf("\n***********************************************\n");
    printf("- %s- Receive %s's Request From %s \n- Requst Id : %d\n", asctime(gmtime(&timep)), PKT.tlvValue1, inet_ntoa(client.sin_addr), PKT.id); /* 输出收到报文的源ip */

    switch (identifyResult)/* 进行验证结果的输出 */
    {
    case 0: /* 错误的信息 */
        printf("- Identify Result :\033[22;31m %s \033[0m\n\n", errorAccount);
        break;
    case 1: /* 普通用户 */
        printf("- Identify Result :\033[22;32m %s \033[0m\n\n", generalUser);
        break;
    case 2: /* 管理员 */
        printf("- Identify Result :\033[22;34m %s \033[0m\n\n", administrator);
        break;
    default:
        break;
    }
    /* printRequestPkt(PKT); */

    /******************************************************************
    * 报文发送
    ******************************************************************/

    bulidReplyPacket(sendmsg, identifyResult, PKT.id); /* 封装回复报文 */
    sendmsg[23] = '\0';
    sendto(sockfd, sendmsg, MAX_BUF_SIZE, 0, (struct sockaddr *)&client, len); /* 发送回复报文 */


    pthread_exit(NULL);
}

/****************************************
* DESCRIPTION:
*     打印请求报文
* INPUTS:
*     PKT - 请求报文结构体,输入为欲打印的请求报文
* OUTPUTS:
*     none
* RETURNS:
*     none
****************************************/
void printRequestPkt(struct request PKT)
{
    INT32 i = 0;
    printf("code=%d\n", PKT.code);
    printf("id=%d\n", PKT.id);
    printf("rLength=%d\n", PKT.requestLength);
    printf("auth=");
    for (i = 0; i < 16; i++)
    {
        printf("%x", PKT.auth[i]);
    }
    printf("\n");
    printf("tlvType1=%d\n", PKT.tlvType1);
    printf("tlvLength1=%d\n", (INT32)PKT.tlvLength1);
    printf("tlvValue1=");
    for (i = 0; i < (INT32)PKT.tlvLength1 - 2; i++)
    {
        printf("%c", PKT.tlvValue1[i]);
    }
    printf("\n");
    printf("tlvType2=%d\n", PKT.tlvType2);
    printf("tlvLength2=%d\n", (INT32)PKT.tlvLength2);
    printf("tlvValue2=");
    for (i = 0; i < (INT32)PKT.tlvLength2 - 2; i++)
    {
        printf("%d", PKT.tlvValue2[i]);
    }
    printf("\n\n");
}

4. server.c


#include "server_fns.h" /* 包含所有所需所有头文件及函数定义 */

INT32 main()
{
    /* 定义参数 */
    INT32 sockfd = 0;                            /* 文件描述符 */
    INT32 msgNum = 0;                            /* 存储缓冲区长度 */
    INT32 i = 0;                                 /* 循环参数 */
    INT32 temp = 0;                              /* 临时变量 */
    INT32 ret = 0;                               /* 线程返回值 */
    pthread_t threaid = 0;                       /* 线程id */
    UCHAR recvmsg[MAX_BUF_SIZE];                 /* 接收缓冲区 */
    UCHAR sendmsg[24];                           /* 发送缓冲区 */
    socklen_t len = sizeof(struct sockaddr_in);  /* 地址结构长度 */

    /* 定义地址信息存储结构体 */
    struct sockaddr_in server;      /* 保存服务器主机地址信息 */
    struct sockaddr_in client;      /* 保存客户端主机地址信息 */
    struct request PKT;             /* 保存客户端发送的请求报文信息*/
    bzero(&server, sizeof(server)); /* 初始化server */
    bzero(&client, sizeof(client)); /* 初始化client */

    /* 创建套接字并进行验证 */
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("Creating socket failed.");
        exit(1);
    }

    /* 初始化服务端信息 */
    server.sin_family = AF_INET;                /* 初始化服务端使用IPV4*/
    server.sin_port = htons(PORT);              /* 初始化服务端主机端口 */
    server.sin_addr.s_addr = htonl(INADDR_ANY); /* 初始化服务端IP地址,INADDR_ANY代表任意地址 */

    /* 绑定套接口并进行验证 */
    if (bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1)
    {
        perror("Bind error.");
        exit(1);
    }
    printf("The Server Is Running......\n");

    /* 循环进行接受与发送 */
    while (1)
    {
        threaid = 0;
        ret = 0;
        ret = pthread_create(&threaid, NULL, thread, (void *)(void *)&sockfd); /* 创建线程*/
        if (ret != 0)
        {
            perror("pthread create sucess\n");
        }
        pthread_join(threaid, NULL); /* 等待线程结束*/

    }

    /* 关闭套接字 */
    close(sockfd);
    return 0;
}

5. md5.h


#ifndef MD5_H
#define MD5_H

typedef struct
{
    unsigned int count[2];
    unsigned int state[4];
    unsigned char buffer[64];
}MD5_CTX;

#define F(x,y,z) ((x & y) | (~x & z))
#define G(x,y,z) ((x & z) | (y & ~z))
#define H(x,y,z) (x^y^z)
#define I(x,y,z) (y ^ (x | ~z))
#define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n)))
#define FF(a,b,c,d,x,s,ac) \
{ \
    a += F(b, c, d) + x + ac; \
    a = ROTATE_LEFT(a, s); \
    a += b; \
}
#define GG(a,b,c,d,x,s,ac) \
{ \
    a += G(b, c, d) + x + ac; \
    a = ROTATE_LEFT(a, s); \
    a += b; \
}
#define HH(a,b,c,d,x,s,ac) \
{ \
    a += H(b, c, d) + x + ac; \
    a = ROTATE_LEFT(a, s); \
    a += b; \
}
#define II(a,b,c,d,x,s,ac) \
{ \
    a += I(b, c, d) + x + ac; \
    a = ROTATE_LEFT(a, s); \
    a += b; \
}
void MD5Init(MD5_CTX *context);
void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen);
void MD5Final(MD5_CTX *context, unsigned char digest[16]);
void MD5Transform(unsigned int state[4], unsigned char block[64]);
void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len);
void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len);

#endif
2012-12-08 11:44:22 huangminqiang201209 阅读数 3711
  • TCP/IP/UDP Socket通讯开发实战 适合iOS/Android/...

    本课程适合中学员,适用于从事iOS/Android/嵌入式Linux网络通讯开发的学员。实战案例可用于无人机,安防,直播等。从Linux音频,视频采集,到TCP/IP UDP Socket基础概念,网络编程接口介绍,POSIX线程封装,私有协议定义,开发,服务器模型,客户端编程等详细实战讲解,整个过程,涵盖iOS,Android ,Mac OS嵌入式Linux网络编程核心的大量实用场景。让学员能够掌握相关知识,融汇贯通掌握网络通讯开发核心知识。 付费学员加入QQ群,可获得本人未来1~3年学习过程中的专业指导解答。第三节课第7分15秒有QQ群,欢迎付费学员加入探讨技术问题。

    14728 人正在学习 去看看 陈超

TCP、UDP、广播、多播的客户端服务器代码链接地址为(for free):

tcp代码:http://download.csdn.net/detail/huangminqiang201209/4860661
udp代码:http://download.csdn.net/detail/huangminqiang201209/4860665
广播代码:http://download.csdn.net/detail/huangminqiang201209/4860672
多播代码:http://download.csdn.net/detail/huangminqiang201209/4860719

 

    此文主要还是在于上面的代码,我这段时间因为要构建一个TCP服务器,所以就把socket这块完整的熟悉了一下,以下这些整理的比较随意,还望见谅哦吐舌头

TCP

    Transmission Control Protocol 传输控制协议TCP是一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议,由IETF的RFC 793说明(specified)。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,UDP是同一层内另一个重要的传输协议。  
     在因特网协议族(Internet protocol suite)四层协议中,TCP层是位于IP层之上,应用层之下的传输层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。  

 

UDP
     UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。

 

广播

     广播和多播仅应用于UDP,它们对需将报文同时传往多个接收者的应用来说十分重要。TCP是一个面向连接的协议,它意味着分别运行于两主机(由IP地址确定)内的两进程(由端口号确定)间存在一条连接。
    考虑包含多个主机的共享信道网络如以太网。每个以太网帧包含源主机和目的主机的以太网地址(48 bit)。通常每个以太网帧仅发往单个目的主机,目的地址指明单个接收接口,因而称为单播(unicast)。在这种方式下,任意两个主机的通信不会干扰网内其他主机(可能引起争夺共享信道的情况除外)。然而,有时一个主机要向网上的所有其他主机发送帧,这就是广播。通过ARP和RARP可以看到这一过程。多播(multicast) 处于单播和广播之间:帧仅传送给属于多播组的多个主机。

 

多播

    多播数据仅由对该数据报感兴趣的接口接收,也就是说,由运行希望参加多播会话应用系统的主机上的接口接收。广播一般局限与局域网,而多播既可用于局域网,也可用于广域网。
IP多播提供两类服务:
    1) 向多个目的地址传送数据。有许多向多个接收者传送信息的应用:例如交互式会议系统和向多个接收者分发邮件或新闻。如果不采用多播,目前这些应用大多采用TCP来完成(向每个目的地址传送一个单独的数据复制)。然而,即使使用多播,某些应用可能继续采用TCP来保证它的可靠性。
    2) 客户对服务器的请求。例如,无盘工作站需要确定启动引导服务器。目前,这项服务是通过广播来提供的,但是使用多播可降低不提供这项服务主机的负担。

 

函数

1.socket 函数
   指定期望的通信协议类型。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

 int socket(int domain, int type, int protocol);
        返回:若成功则为非负描述符,出错则为-1。

参数说明:
 domain:   指明协议族,也称为协议域,是一个常值。
           AF_INET              IPv4 协议
           AF_INET6             IPv6 协议
           AF_LOCAL/AF_UNIX       Unix协议域
           AF_ROUTE                 路由套接字
           AF_KEY                   密匙套接字
    
 type:    指明套接字的类型。
           SOCK_STREAM             字节流套接字(TCP)
           SOCK_DGRAM             数据报套接字(UDP)
           SOCK_SEQPACKET        有序分组套接字
           SOCK_RAW               原始套接字
   
 protocol: 指明协议类型。一般为0,以选择给定的domain和type组合的系统默认值。
           IPPROTO_TCP           TCP传输协议
           IPPROTO_UDP           UDP传输协议
           IPPROTO_SCTP          SCTP传输协议
函数描述:
    socket 函数在成功时返回一个小的非负整数值,与文件描述符类似,我们称它为套接字描述符,简称 sockfd。为了得到这个套接字描述符,我们只是指定了协议族(IPv4、IPv6
    或Unix)和套接字类型(字节流、数据报或原始套接字)。我们并没有指定本地跟远程的协议地址。
    
2.bind 函数    
   将一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址和128位的IPv6地址与16位的TCP或UDP端口号的组合。bind 函数主要用于服务器端,用来指定本地
   主机的哪个网络接口(IP,可以是INADDR_ANY,表示本地主机的任一网络接口)可以接受客户端的请求,和指定端口号(即开启的等待客户来连接的进程)。
 
#include <sys/socket.h>
 int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
         返回:若成功则为0,出错则为-1。

参数说明:
    sockfd:          socket 函数返回的套接字描述符。
    myaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。

 struct sockaddr结构说明如下:
     struct sockaddr_in {
   short int sin_family;   /* 地址族 */
   unsigned short int sin_port;  /* 端口号 */
   struct in_addr sin_addr;   /* IP地址 */
   unsigned char sin_zero[8];  /* 填充0 以保持与struct sockaddr同样大小 */
     };

函数描述:
    对于 TCP ,调用 bind 函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以两者都不指定。
    服务器在启动时捆绑它们众所周知的端口号(如何捆绑?)。如果一个TCP客户或服务器未曾调用bind捆绑一个端口,当调用 connect 或 listen 时,内核就要为相应的套接字选择一个临时端口。
    让内核来选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口。然而对于TCP服务器来说却极为罕见,因为服务器是通过它们的众所周知的端口号来被大家认识的。
 
    进程可以把一个特定的IP捆绑到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之一(对于TCP服务器)。对于TCP客户,这就为在该套接字上发送的IP数据报指派了源IP地址(服务器源地址)。对于TCP服务器,这就限定该套接字只接收那些目的地为这个IP地址的客户连接。TCP套接字通常不把IP地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源IP地址,而所用外出端口则取决于到达服务器所需的路径。如果TCP服务器没有把IP地址捆绑到它的套接字上,内核就会把发送的SYN的目的IP地址作为服务器的源IP地址(即服务器IP等于INADDR_ANY的情况)。
    实际上客户的源IP地址就是服务器的目的地址,服务器的源IP地址就是客户的目的地址,说到底也就只存在两个IP地址:客户IP跟服务器IP。
 
3.connec函数
t    TCP 客户用 connect 函数来与 TCP 服务器建立连接。
  
#include <sys/socket.h>
 int connect( int sockfd, const struct sockaddr *servaddr, socklen_t addrlen );
 返回:若成功则为0,出错则为-1。

参数说明:
    sockfd:            由 socket 函数返回的套接字描述符。    
    servaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。套接字地址结构必须含有服务器的IP地址和端口号。

函数描述:
    客户在调用 connect 函数前并不一定得调用 bind 函数,如果需要的话,内核会确定源P地址,并选择一个临时端口作为源端口。所以在客户进程中的套接字一般只需指明客户所要连接的服务器的IP跟端口号。
    如果是 TCP 套接字,调用 connect 函数将激发 TCP 的三路握手。而且仅在连接成功或出错时才返回。其中出错的情况有如下几种:
    1-> TCP 客户没有收到 SYN 分节的响应。
    2-> TCP 服务器对客户的 SYN 分节的响应是 RST 。
    3-> 客户发出的 SYN 分节在某个路由器器上发生了错误。
    若 connect 调用失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次执行 connect 函数。

 

4.listen 函数
#include <sys/socket.h>
 int listen(int sockfd, int backlog);
         返回:若成功则为0,出错则为-1。

函数描述:        
    listen 函数仅由 TCP 服务器调用,它做两件事情。
    (1)把一个未连接的套接字(主动)转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求。
    (2)backlog 参数规定了内核应该为相应套接字排队的最大连接数。其中内核始终为监听套接字维护两个队列。
       (1)未完成连接队列,每个SYN分节对于其中一项:
           已由某个客户发出并到达服务器,而服务器正在等待待完成的TCP三路握手过程。这些套接字处于SYN_RCVD状态。
       (2)已完成连接队列
          每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
          backlog 就是这两个队列和的最大值。
    在三路握手完成之后,但在服务器调用 accept 之前到达的数据应由 TCP 服务器排队,最大数据量为相应已连接套接字的接收缓冲区的大小。

 

5.accept 函数
    accept 函数由 TCP 服务器调用,用于从已完成连接队列头返回下一个已完成连接。如果已完成队列为空,那么进程被投入睡眠(假设套接字为默认的阻塞方式)。
#include <sys/socket.h>
int accept(int sockfd ,struct sockaddr *cliaddr, socklen_t *addrlen);
        返回:若成功则为非负已连接描述符和对端的IP和端口号,出错则为-1。

参数说明:
    cliaddr、addrlen 用来返回已连接的对端进程(客户)的协议地址 。调用前,我们将由 *addrlen 所引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为内核存放在该套接字地址机构内的确切字节数。

函数描述:
    如果 accept 调用成功,那么其返回值是由内核自动生成的一个全新描述符,代表着与所返回客户的TCP连接。在讨论 accept 函数时,我们称它的第一个参数为监听套接字描述符(由 socket 创建,随后用作bind 和 listen 的第一个参数的描述符),称它的返回值为已连接套接字描述符。区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在服务器的生命期内一直存在。内核为每个服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个连接客户的服务时,相应的已连接套接字就要被关闭。

 

6.recv/recvfrom函数
    从套接字上接收一个消息。对于recvfrom ,可同时应用于面向连接的和无连接的套接字。recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置NULL。如果消息太大,无法完整存放在所提供的缓冲区,根据不同的套接字,多余的字节会丢弃。假如套接字上没有消息可以读取,除了套接字已被设置为非阻塞模式,否则接收调用会等待消息的到来。

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sock, void *buf, size_t len, int flags);
ssize_t recvfrom(int sock, void *buf, size_t len, int flags,  struct sockaddr *from, socklen_t *fromlen);

参数:  
sock:索引将要从其接收数据的套接字。
buf:存放消息接收后的缓冲区。
len:buf所指缓冲区的容量。
flags:是以下一个或者多个标志的组合体,可通过or操作连在一起
MSG_DONTWAIT:操作不会被阻塞。
MSG_ERRQUEUE:指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供。错误以sock_extended_err结构形态被使用,定义如下
#define SO_EE_ORIGIN_NONE    0
#define SO_EE_ORIGIN_LOCAL   1
#define SO_EE_ORIGIN_ICMP    2
#define SO_EE_ORIGIN_ICMP6   3
struct sock_extended_err
{
    u_int32_t ee_errno;   /* error number */
    u_int8_t ee_origin; /* where the error originated */
    u_int8_t ee_type;    /* type */
    u_int8_t ee_code;    /* code */
    u_int8_t ee_pad;
    u_int32_t ee_info;    /* additional information */
    u_int32_t ee_data;    /* other data */
    /* More data may follow */
};

MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
MSG_EOR:指示记录的结束,返回的数据完成一个记录。
MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
from:指向存放对端地址的区域,如果为NULL,不储存对端地址。
fromlen:作为入口参数,指向存放表示from最大容量的内存单元。作为出口参数,指向存放表示from实际长度的内存单元。

 

7.send/sendto函数
     用于发送消息。send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。sendto 和 sendmsg既可用于无连接的套接字,也可用于基于连接的套接字。除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。
 
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sock, const void *buf, size_t len, int flags);
ssize_t sendto(int sock, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
ssize_t sendmsg(int sock, const struct msghdr *msg, int flags);

参数: 
sock:索引将要从其发送数据的套接字。
buf:指向将要发送数据的缓冲区。
len:以上缓冲区的长度。len可以大于sizeof(buf),例如send("123",10)-->则实际发送10字节数据
flags:是以下零个或者多个标志的组合体,可通过or操作连在一起
MSG_DONTROUTE:不要使用网关来发送封包,只发送到直接联网的主机。这个标志主要用于诊断或者路由程序。
MSG_DONTWAIT:操作不会被阻塞。
MSG_EOR:终止一个记录。
MSG_MORE:调用者有更多的数据需要发送。
MSG_NOSIGNAL:当另一端终止连接时,请求在基于流的错误套接字上不要发送SIGPIPE信号。
MSG_OOB:发送out-of-band数据(需要优先处理的数据),同时现行协议必须支持此种操作。
to:指向存放接收端地址的区域,可以为NULL。
tolen:以上内存区的长度,可以为0。
msg:指向存放发送消息头的内存缓冲,结构形态如下
struct msghdr {
    void           *msg_name;     
    socklen_t      msg_namelen;  
    struct iovec  *msg_iov;      
    size_t          msg_iovlen;   
    void           *msg_control;  
    socklen_t      msg_controllen;
    int             msg_flags;    
};
可能用到的数据结构有
struct cmsghdr {
    socklen_t cmsg_len;   
    int       cmsg_level; 
    int       cmsg_type; 
};

 

8.getsockname 函数
  获取一个套接口的本地名字。
#include <sys/socket.h>
    int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
         返回:成功则不返回,出错则为-1。

参数:
 sockfd:标识一个已捆绑套接口的描述字。   
   localaddr:接收套接口的地址(名字)。   
   addrlen:名字缓冲区长度。

函数描述:
      getsockname()函数用于获取一个套接字的名字。它用于一个已捆绑或已连接套接字sockfd,本地地址将被返回。本调用特别适用于如下情况:未调用bind()就调用了connect(),这时唯有getsockname()调用可以获知系统内定的本地地址。在返回时,namelen参数包含了名字的实际字节数。   
     若一个套接字与INADDR_ANY捆绑,也就是说该套接字可以用任意主机的地址,此时除非调用connect()或accept()来连接,否则getsockname()将不会返回主机IP地址的任何信息。除非套接字被连接,WINDOWS套接字应用程序不应假设IP地址会从INADDR_ANY变成其他地址。这是因为对于多个主机环境下,除非套接字被连接,否则该套接字所用的IP地址是不可知的。

 

9.getpeername 函数函数
  获取与套接口相连的端地址。
 int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
         返回:成功则不返回,出错则为-1。

函数描述:
 getpeername()函数用于从端口sockfd中获取与它捆绑的端口名,并把它存放在sockaddr类型的name结构中。它适用于数据报或流类套接口。

 

10.setsockopt/getsockopt函数
 设置/获取套接口选项
  #include <winsock.h>
  int setsockopt(int sockfd, int level, int optname, void *optval,  int optlen);
  int getsockopt(int sockfd, int level, int optname, void *optval,  int *optlen) ;

参数:
  s:标识一个套接口的描述字。
  level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
  optname:需设置的选项。
  optval:指针,指向存放选项值的缓冲区。
  optlen:optval缓冲区的长度。

注释:
        setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。

 

 

 

2017-05-24 21:48:26 weixin_37895339 阅读数 1718
  • TCP/IP/UDP Socket通讯开发实战 适合iOS/Android/...

    本课程适合中学员,适用于从事iOS/Android/嵌入式Linux网络通讯开发的学员。实战案例可用于无人机,安防,直播等。从Linux音频,视频采集,到TCP/IP UDP Socket基础概念,网络编程接口介绍,POSIX线程封装,私有协议定义,开发,服务器模型,客户端编程等详细实战讲解,整个过程,涵盖iOS,Android ,Mac OS嵌入式Linux网络编程核心的大量实用场景。让学员能够掌握相关知识,融汇贯通掌握网络通讯开发核心知识。 付费学员加入QQ群,可获得本人未来1~3年学习过程中的专业指导解答。第三节课第7分15秒有QQ群,欢迎付费学员加入探讨技术问题。

    14728 人正在学习 去看看 陈超

简要介绍UDP原理,通过代码实例讲解。
这里写图片描述
本篇博客不强调server跟client 的概念,重在实现双方互通。
收的一方: socket()->bind()->recvfrom()->close()
发的一方:socket()->sendto()->close()
只有收数据的一方需要bind(),而发送的一方不需要bind()。由上图可以看出,bind()的一方只有收到消息(recvfrom)后才能给另一方发送消息。
同一台电脑如果要实现互发消息,则无法同时bind()同一个端口号,两方需要bind()不同的端口号才能实现自由互发,否则bind()的一方将会出现阻塞等待消息的现象。
下面代码中介绍UDP的C++实现。

1.设置两个端口实现互发数据
接受一方:

#include<sys/select.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>

int main(){

    //同一台电脑测试,需要两个端口
    int port_in  = 12321;
    int port_out = 12322;
    int sockfd;

    // 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1==sockfd){
        return false;
        puts("Failed to create socket");
    }

    // 设置地址与端口
    struct sockaddr_in addr;
    socklen_t          addr_len=sizeof(addr);

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;       // Use IPV4
    addr.sin_port   = htons(port_out);    //
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // Time out
    struct timeval tv;
    tv.tv_sec  = 0;
    tv.tv_usec = 200000;  // 200 ms
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));

    // Bind 端口,用来接受之前设定的地址与端口发来的信息,作为接受一方必须bind端口,并且端口号与发送方一致
    if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){
        printf("Failed to bind socket on port %d\n", port_out);
        close(sockfd);
        return false;
    }

    char buffer[128];
    memset(buffer, 0, 128);

    int counter = 0;
    while(1){
        struct sockaddr_in src;
        socklen_t src_len = sizeof(src);
        memset(&src, 0, sizeof(src));

        int sz = recvfrom(sockfd, buffer, 128, 0, (sockaddr*)&src, &src_len);
        if (sz > 0){
            buffer[sz] = 0;
            printf("Get Message %d: %s\n", counter++, buffer);
        }
        else{
            puts("timeout");
        }
    }

    close(sockfd);
    return 0;
}

发送一方:

#include<sys/select.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>

int main(){
    int port_in  = 12321;
    int port_out = 12322;
    int sockfd;

    // 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1==sockfd){
        return false;
        puts("Failed to create socket");
    }

    // 设置地址与端口
    struct sockaddr_in addr;
    socklen_t          addr_len=sizeof(addr);

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;       // Use IPV4
    addr.sin_port   = htons(port_in);    //
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // Time out
    struct timeval tv;
    tv.tv_sec  = 0;
    tv.tv_usec = 200000;  // 200 ms
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));

    // 绑定获取数据的端口,作为发送方,不绑定也行
    if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){
        printf("Failed to bind socket on port %d\n", port_in);
        close(sockfd);
        return false;
    }

    int counter = 0;
    while(1){


        addr.sin_family = AF_INET;
        addr.sin_port   = htons(port_out);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);

        sendto(sockfd, "hello world", 11, 0, (sockaddr*)&addr, addr_len);
        printf("Sended %d\n", ++counter);
        sleep(1);
    }

    close(sockfd);
    return 0;
}

2.设置一个端口号实现互发数据
server方必须收到一次client方的数据后,才能实现随意互发数据。
使用方法为:运行server->运行client->键盘在client输入->随意互发数据。
server:

//只有在server接收到消息后才能实现互发数据
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888//唯一端口号
int main(){
    int ser_sockfd;
    int len;
    fd_set rfds;
    socklen_t addrlen;
    char seraddr[100];
    struct sockaddr_in ser_addr;
    int retval, maxfd;
    //建立socket
    ser_sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(ser_sockfd<0){
        printf("I cannot socket success\n");
        return 1;
     }
    //设置地址与端口
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    ser_addr.sin_port= htons (SERVER_PORT);
    //server绑定,才能接受client的数据
    if(bind(ser_sockfd,(struct sockaddr *)&ser_addr,addrlen)<0){
        printf("connect");
        return 1;
    }
    while(1){
        bzero(seraddr,sizeof(seraddr));
        len=recvfrom(ser_sockfd,seraddr,sizeof(seraddr),0,(struct sockaddr*)&ser_addr,&addrlen);
        /*显示client端的网络地址*/
        printf("receive from %s\n",inet_ntoa(ser_addr.sin_addr));
        /*显示客户端发来的字串*/
        printf("recevce:%s",seraddr);
        /*输入字串返回给client端*/
        while(1)
        {
             /*把可读文件描述符的集合清空*/
             FD_ZERO(&rfds);
             /*把标准输入的文件描述符加入到集合中*/
             FD_SET(0, &rfds);
             maxfd = 0;
             /*把当前连接的文件描述符加入到集合中*/
             FD_SET(ser_sockfd, &rfds);
             /*找出文件描述符集合中最大的文件描述符*/
             if(maxfd < ser_sockfd)
                maxfd = ser_sockfd;
             retval = select(maxfd+1, &rfds, NULL, NULL, NULL);
             if(FD_ISSET(ser_sockfd,&rfds))//client发消息来会出发进入
             {
             len=recvfrom(ser_sockfd,seraddr,sizeof(seraddr),0,(struct sockaddr*)&ser_addr,&addrlen);                   printf("recevce:%s",seraddr);
              }
                 if(FD_ISSET(0, &rfds))//键盘输入会触发进入
                 {
                     len=read(STDIN_FILENO,seraddr,sizeof(seraddr));
                     sendto(ser_sockfd,seraddr,len,0,(struct sockaddr*)&ser_addr,addrlen);
                 }
        }
    }
    return 0;
}

client:

#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888   //唯一端口
int main(int argc,char **argv){
    int cli_sockfd;
    int len;
    fd_set rfds;
    socklen_t addrlen;
    struct sockaddr_in cli_addr;
    char buffer[256];
    char buffer1[256];
    int retval, maxfd;
    //创建socket
    cli_sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(cli_sockfd<0){
        printf("I cannot socket success\n");
        return 1;
    }
    //配置地址与端口
    addrlen=sizeof(struct sockaddr_in);
    bzero(&cli_addr,addrlen);
    cli_addr.sin_family=AF_INET;
    cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);//任何主机地址
    cli_addr.sin_port=htons(SERVER_PORT);
//这里作为发送方,不需要绑定bind()
    while(1)
    {
        bzero(buffer,sizeof(buffer));
        /* 从标准输入设备取得字符串*/   len=read(STDIN_FILENO,buffer,sizeof(buffer));
        /* 将字符串传送给server端*/
        sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
        /* 接收server端返回的字符串*/
        while(1)
        {
                     /*把可读文件描述符的集合清空*/
                     FD_ZERO(&rfds);
                     /*把标准输入的文件描述符加入到集合中*/
                     FD_SET(0, &rfds);
                     maxfd = 0;
                     /*把当前连接的文件描述符加入到集合中*/
                     FD_SET(cli_sockfd, &rfds);
                     /*找出文件描述符集合中最大的文件描述符*/
                     if(maxfd < cli_sockfd)
                         maxfd = cli_sockfd;

                     retval = select(maxfd+1, &rfds, NULL, NULL, NULL);

                    if(FD_ISSET(cli_sockfd,&rfds))//server发来数据将会触发进入循环
                    {

                        len=recvfrom(cli_sockfd,buffer1,sizeof(buffer1),0,(struct sockaddr*)&cli_addr,&addrlen);
                        printf("receive: %s",buffer1);
                    }
                     if(FD_ISSET(0, &rfds))//键盘输入会触发进入
                     {
                         len=read(STDIN_FILENO,buffer,sizeof(buffer));
                         sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
                     }

        }
    }
    close(cli_sockfd);
    return 0;
}

补充:inet_addr(“127.0.0.1”)将一个点分十进制的IP转换成一个长整数型数(u_long类型)。

WALDM

没有更多推荐了,返回首页