網絡編程2----UDP通信









一 UDP通信框架


UDPSend.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int arg, char *args[])
{
	if (arg < 3)
		return -1;

	int st = socket(AF_INET, SOCK_DGRAM, 0);//建立socket的時候第二個參數值爲SOCK_DGRAM
	if (st == -1)
	{
		printf("socket failed %s\n", strerror(errno));
		return 0;
	}

	int port = atoi(args[2]);


	int on = 1;
	if (setsockopt(st, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)//設置UDP socket可以發送廣播消息
	{
		printf("setsockopt failed %s\n", strerror(errno));
		return EXIT_FAILURE;
	}



	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(args[1]);
	char buf[1024];
	while (1)
	{
		memset(buf, 0, sizeof(buf));
		read(STDIN_FILENO, buf, sizeof(buf));//讀取用戶鍵盤輸入
		if (sendto(st, buf, strlen(buf), 0, (struct sockaddr *) &addr,
			sizeof(addr)) == -1)//udp使用sendto發送消息
		{
			printf("sendto failed %s\n", strerror(errno));
			break;
		}
	}
	close(st);
	return EXIT_SUCCESS;
}


UDPRecv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int arg, char *args[])
{
	if (arg < 2)
		return -1;

	int st = socket(AF_INET, SOCK_DGRAM, 0);
	if (st == -1)
	{
		printf("socket failed %s\n", strerror(errno));
		return 0;
	}

	int port = atoi(args[1]);
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)//UDP接收數據,也需要綁定IP,不需要監聽和connnect
	{
		printf("bind failed %s\n", strerror(errno));
		return -1;
	}
	char buf[1024];
	struct sockaddr_in client_addr;
	socklen_t len = sizeof(client_addr);
	while (1)
	{
		memset(&client_addr, 0, sizeof(client_addr));
		memset(buf, 0, sizeof(buf));
		if (recvfrom(st, buf, sizeof(buf), 0,
			(struct sockaddr *)&client_addr, &len) == -1)
		{
			printf("recvfrom failed %s\n", strerror(errno));
			break;
		}
		else
		{

			printf("%s recv is %s\n", inet_ntoa(client_addr.sin_addr), buf);
		}
	}
	close(st);
	return 0;
}

makefile文件

.SUFFIXES:.c .o
CC=gcc
SRCS1=udpsend.c
SRCS2=udprecv.c
OBJS1=$(SRCS1:.c=.o)
OBJS2=$(SRCS2:.c=.o)
EXEC1=udprecv
EXEC2=udpsend

start:$(OBJS2) $(OBJS1)
	$(CC) -o $(EXEC2) $(OBJS2) -lpthread
	$(CC) -o $(EXEC1) $(OBJS1) -lpthread
	@echo "======OK!=========\n"
.c.o:
	$(CC) -o $@ -c $<
clean:
	rm -r $(OBJS2) $(EXEC2) $(EXEC1) $(OBJS1)


二 UDP文件傳輸系統


client.c
#include <stdio.h>
#include <stdlib.h>
#include "pub.h"

int main(int arg, char *args[])
{
	if (arg < 4)//如果參數小於3個,main函數退出
	{
		printf("Input argumnet: source | IP | Port | resource\n");
		return EXIT_FAILURE;
	}

	int iport = atoi(args[2]);//將第二個參數轉化爲端口號
	if (iport == 0)//如果端口號爲0,main函數退出
	{
		printf("port %d is invalid\n", iport);
		return EXIT_FAILURE;
	}

	printf("%s send begin\n", args[3]);
	if (send_work(args[1], iport, args[3]) == 1)
		printf("%s send success\n", args[3]);
	else
		printf("%s send fail\n", args[3]);

	return EXIT_SUCCESS;
}


server.c
#include <stdio.h>
#include <stdlib.h>
#include "pub.h"

int main(int arg, char *args[])
{
	if (arg < 2)//如果沒有參數,main函數退出
	{
		printf("Input like: source | port\n");
		return EXIT_FAILURE;
	}

	int iport = atoi(args[1]);//將第一個參數轉化爲端口號,server端socket要在這個端口號上listen
	if (iport == 0)
	{
		printf("port %d is invalid\n", iport);
		return EXIT_FAILURE;
	}

	printf("recv is begin\n");
	if (recv_work(iport) == 1)//server端socket在port指定的端口上listen,接收來自client發送的文件
		printf("recv success\n");
	else
		printf("recv fail\n");
	return EXIT_SUCCESS;
}

pub.h
#ifndef PUB_H_
#define PUB_H_

int send_work(const char *hostname, int port, const char *filename);
int recv_work(int port);

#endif

pub.c
#ifdef WIN
#include <WinSock2.h>
#else
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#define SOCKET int
#endif

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

#define BUFSIZE 262144  //256k

void getfilename(const char *filename, char *name)//從完整路徑名中解析出文件名稱,例如:/home/test/abc.txt,解析完成後爲abc.txt
{
	int len = strlen(filename);
	int i;
	for (i = (len - 1); i >= 0; i--)
	{
		if ((filename[i] == '\\') || (filename[i] == '/'))
		{
			break;
		}
	}
	strcpy(name, &filename[i + 1]);
	return;
}

SOCKET init_socket()//初始化socket
{
	//如果是windows,執行如下代碼
#ifdef WIN
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return -1;
	}

	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
		WSACleanup();
		return -1;
	}
#endif

	return 0;
}

SOCKET create_recv_socket(int port)//在port指定的端口上建立接收數據的IDP SOCKET
{
	//if (init_socket() == -1)
		//return;
	SOCKET st = socket(AF_INET, SOCK_DGRAM, 0);//建立socket的時候第二個參數值爲SOCK_DGRAM
	if (st == 0)
		return 0;//失敗,返回0

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(INADDR_ANY);
	
	if (bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)//UDP接收數據,也需要綁定IP,不需要監聽和connnect
	{
		printf("bind failed %s\n", strerror(errno));
		return 0;
	}

	return st;
}

SOCKET create_send_socket()//建立發送數據的UDP Socket
{
	if (init_socket() == -1)
		return;
	SOCKET st = socket(AF_INET, SOCK_DGRAM, 0);//建立socket的時候第二個參數值爲SOCK_DGRAM
	if (st == 0)
		return 0;//失敗,返回0
	return st;//成功
}

int send_work(const char *hostname, int port, const char *filename)//向hostname指定的IP地址發送filename指定的文件
{

	SOCKET st_send = create_send_socket();//建立發送數據的UDP Socket
	SOCKET st_recv = create_recv_socket(port + 1);//建立接收數據的UDP Socket

	if (st_send == 0)//建立失敗,函數返回
		return 0;

	if (st_recv == 0)
		return 0;

	FILE *fd = fopen(filename, "rb");//以只讀方式打開filename指定的文件
	if (fd == NULL)//如果文件打開失敗,函數返回
	{
		printf("open %s failed %s\n", filename, strerror(errno));
		return 0;
	}
	char *buf = malloc(BUFSIZE);//申請一個緩衝區,存放接收到的文件內容
	memset(buf, 0, BUFSIZE);
	getfilename(filename, buf);//從完整路徑名中解析出文件名稱,例如:/home/test/abc.txt,解析完成後爲abc.txt

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(hostname);

	size_t rc = sendto(st_send, buf, strlen(buf), 0, (struct sockaddr *) &addr,sizeof(addr));//客戶端第一次給server端發送的數據爲要傳遞的文件名稱,將解析完成後的文件名通過socket發送給server端
	if (rc <= 0)//發送失敗
	{
		printf("send failed %s\n", strerror(errno));
	}
	else //發送成功
	{
		struct sockaddr_in client_addr;

#ifdef WIN
		int len = 0;
#else
		unsigned int len = 0;
#endif
		len = sizeof(client_addr);
		memset(&client_addr, 0, sizeof(client_addr));

		memset(buf, 0, BUFSIZE);
		if (recvfrom(st_recv, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &len) <= 0)//接收來自server端的回覆
		{
			printf("recv failed %s\n", strerror(errno));
		}
		else
		{
			if (strncmp(buf, "OK", 2) == 0)//如果收到來自服務端的回覆,代表服務端認可,可以發送文件了
			{
				while (1)
				{
					memset(buf, 0, BUFSIZE);
					rc = fread(buf, 1, BUFSIZE, fd);//循環讀取文件,直到讀到文件尾,循環break
					if (rc <= 0)
					{
						break;//如果到文件最後,循環終止
					}
					else
					{
						rc = sendto(st_send, buf, rc, 0, (struct sockaddr *) &addr, sizeof(addr));//將從文件中讀到的數據,通過socket發送到server端,其中rc爲從文件中讀到的數據大小
						if (rc <= 0)//如果發送失敗,代表TCP連接出錯,循環break
						{
								printf("send failed %s\n", strerror(errno));
						}
					}
				}
			}
			memset(buf, 0, BUFSIZE);
			rc = sendto(st_send, buf, 128, 0, (struct sockaddr *) &addr, sizeof(addr));//連續發送128個0字節,代表文件發送完畢
		}
	}

	fclose(fd);
	free(buf);

#ifdef WIN	
	closesocket(st_send);
	closesocket(st_recv);
	WSACleanup();
#else	
	close(st_send);
	close(st_recv);
#endif
	return 1;
}

int recv_work(int port)//server端socket在port指定的端口上listen,接收來自client發送的文件
{
	SOCKET st_send = create_send_socket();//建立發送數據的UDP Socket
	SOCKET st_recv = create_recv_socket(port + 1);//建立接收數據的UDP Socket

	if (st_send == 0)//建立失敗,函數返回
		return 0;

	if (st_recv == 0)
		return 0;

	char *buf = malloc(BUFSIZE);//建立接收文件數據緩衝區
	FILE *fd = NULL;

#ifdef WIN
	int len = 0;
#else
	unsigned int len = 0;
#endif

	struct sockaddr_in client_addr;
	len = sizeof(client_addr);
	memset(&client_addr, 0, len);
	memset(buf, 0, BUFSIZE);
	size_t rc = recvfrom(st_recv, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &len);//接收來自client的數據,客戶端第一次要發送的文件名稱
	if (rc <= 0)
	{
		printf("recv failed %s\n", strerror(errno));
	}
	else
	{
		printf("receiving %s\n", buf);

		fd = fopen(buf, "wb");//以只寫方式打開文件
		if (fd == NULL)
		{
			printf("open %s failed %s\n", buf, strerror(errno));
		}
		else
		{
			client_addr.sin_port = htons(port + 1);//客戶端接收的端口號
			memset(buf, 0, BUFSIZE);
			strcpy(buf, "OK");
			rc = sendto(st_send, buf, strlen(buf), 0, (struct sockaddr *) &client_addr, sizeof(client_addr));//回覆客戶端,同意接收文件
			if (rc <= 0)
			{
				printf("send failed %s\n", strerror(errno));
			}

			while (1)
			{
				memset(buf, 0, BUFSIZE);
				rc = recvfrom(st_recv, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &len);//循環接收來自client的數據,數據爲發送文件的內容
				
				char tmp[128];
				memset(tmp, 0, 128);
				if (memcmp(buf, tmp, sizeof(tmp)) == 0)//連續接收到128Z字節的0,結束
					break;

				if (rc <= 0)//如果client連接斷開,代表文件傳遞完成,或者網絡意外中斷,循環break
				{
					printf("recv failed %s\n", strerror(errno));
					break;
				}
				else
				{
					fwrite(buf, 1, rc, fd);//將從client端收到的內容寫入文件
				}
			}
		}
	}

	if (fd)
		fclose(fd);//關閉打開的文件
	free(buf);

#ifdef WIN	
	closesocket(st_send);
	closesocket(st_recv);
	WSACleanup();
#else	
	close(st_send);
	close(st_recv);
#endif
	return 1;
}


makefile
.SUFFIXES:.c .o
CC=gcc
SRCS1=client.c\
	pub.c
SRCS2=server.c\
	pub.c
OBJS1=$(SRCS1:.c=.o)
OBJS2=$(SRCS2:.c=.o)
EXEC1=client
EXEC2=server

start:$(OBJS2) $(OBJS1)
	$(CC) -o $(EXEC2) $(OBJS2) -lpthread
	$(CC) -o $(EXEC1) $(OBJS1) -lpthread
	@echo "======OK!=========\n"
.c.o:
	$(CC) -o $@ -c $<
clean:
	rm -r $(OBJS2) $(EXEC2) $(EXEC1) $(OBJS1)




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章