1. 程式人生 > >【C/C++學院】(29)網路程式設計--實現跨平臺傳輸檔案(TCP版)

【C/C++學院】(29)網路程式設計--實現跨平臺傳輸檔案(TCP版)

    為了實現跨平臺,需要對跨平臺的程式碼進行條件編譯。

gcc的-D選項。

連線選項
-lWs2_32
代表要用Ws2_32.lib這個庫

gcc編譯選項,-D 代表定義一個巨集,等同於在c語言當中定義 #defind WIN

在windows下,使用socket之前,必須使用WSAStartup初始化socket,程式執行結束以後必須呼叫WSACleanup釋放相關資源

windown下,關閉socket使用closesocket函式

//makefile-win

.SUFFIXES: .c .o
CC=gcc
SERVERSRCS=server.c\
            pub.c
CLIENTSRCS=client.c\
            pub.c
        
SERVEROBJS=$(SERVERSRCS:.c=.o)
CLIENTOBJS=$(CLIENTSRCS:.c=.o)
SERVEREXEC=server.exe
CLIENTEXEC=client.exe
all:$(SERVEROBJS) $(CLIENTOBJS)
    $(CC) -static -o $(SERVEREXEC) $(SERVEROBJS) -lWs2_32
    $(CC) -static -o $(CLIENTEXEC) $(CLIENTOBJS) -lWs2_32 
    @echo '-------------ok--------------'
.c.o:
    $(CC) -Wall -DWIN -o 
[email protected]
-c $< clean: rm -f $(SERVEROBJS) rm -f $(CLIENTOBJS) rm -f core*

//makefile
.SUFFIXES: .c .o
CC=gcc
SERVERSRCS=server.c\
            pub.c
CLIENTSRCS=client.c\
            pub.c
        
SERVEROBJS=$(SERVERSRCS:.c=.o)
CLIENTOBJS=$(CLIENTSRCS:.c=.o)
SERVEREXEC=server
CLIENTEXEC=client
all:$(SERVEROBJS) $(CLIENTOBJS)
    $(CC) -o $(SERVEREXEC) $(SERVEROBJS)
    $(CC) -o $(CLIENTEXEC) $(CLIENTOBJS)
    @echo '-------------ok--------------'
.c.o:
    $(CC) -Wall -o 
[email protected]
-c $< clean: rm -f $(SERVEROBJS) rm -f $(CLIENTOBJS) rm -f core*

//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_H_ */  

//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 socket_connect(const char *hostname, int port)//連線到指定的主機和埠號
{
	if (init_socket() == -1)
		return 0;

	SOCKET st = socket(AF_INET, SOCK_STREAM, 0);//建立TCP socket
	if (st == 0)
		return 0;
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);//指定port為要連線的埠號
	addr.sin_addr.s_addr = inet_addr(hostname);//指定hostname為要連線的IP地址
	if (connect(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	{
		printf("connect to %s:%d failed %s\n", hostname, port, strerror(errno));
		return 0;//連線失敗,返回0
	} else
	{
		return st;//連線成功,返回socket描述符
	}
}

SOCKET socket_create(int port)//在port指定的埠上建立server端socket
{
	if (init_socket() == -1)
		return 0;

	SOCKET st = socket(AF_INET, SOCK_STREAM, 0);//建立TCP socket
	if (st == 0)
		return 0;//如果建立socket失敗,返回0

#ifdef WIN
	const char on = 0;
#else
	int on = 0;
#endif

	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
	{
		printf("setsockopt failed %s\n", strerror(errno));
		return 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 = htonl(INADDR_ANY);
	if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)//server端socket,所以需要繫結IP地址
	{
		printf("bind failed %s\n", strerror(errno));
		return 0;
	}
	if (listen(st, 20) == -1)
	{
		printf("listen failed %s\n", strerror(errno));
		return 0;
	}
	printf("listen %d success\n", port);
	return st;//server端socket建立成功,返回server端socket描述符
}

SOCKET socket_accept(SOCKET listen_st)//server端socket開始accept的函式
{
	struct sockaddr_in client_addr;

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

	len = sizeof(client_addr);
	memset(&client_addr, 0, sizeof(client_addr));
	SOCKET client_st = accept(listen_st, (struct sockaddr *) &client_addr,
			&len);//accept阻塞,直到有client連線到server才返回
	if (client_st == -1)
	{
		printf("accept failed %s\n", strerror(errno));
		return 0;
	} else
	{
		printf("accept by %s\n", inet_ntoa(client_addr.sin_addr));
		return client_st;//有client連線到server,返回client的socket描述符
	}
}

int send_work(const char *hostname, int port, const char *filename)//向hostname指定的IP地址傳送filename指定的檔案
{
	SOCKET st = socket_connect(hostname, port);//連線到hostname指定的IP地址和port指定的埠號
	if (st == 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
	size_t rc = send(st, buf, strlen(buf), 0);//客戶端第一次給server端傳送的資料為要傳遞的檔名稱,將解析完成後的檔名通過socket傳送給server端
	if (rc <= 0)
	{
		if (rc < 0)
			printf("send failed %s\n", strerror(errno));
		else
			printf("socket disconnect\n");

	} else
	{

		memset(buf, 0, BUFSIZE);
		if (recv(st, buf, BUFSIZE, 0) <= 0)//接收來自server端的回覆
		{
			if (rc < 0)
				printf("recv failed %s\n", strerror(errno));
			else
				printf("socket disconnect\n");
		} else
		{
			if (strncmp(buf, "OK", 2) == 0)//如果收到來自服務端的回覆,代表服務端認可,可以傳送檔案了
			{
				while (1)
				{
					memset(buf, 0, BUFSIZE);
					rc = fread(buf, 1, BUFSIZE, fd);//迴圈讀取檔案,直到讀到檔案尾,迴圈break
					if (rc <= 0)
					{
						if (rc < 0)
							printf("fread failed %s\n", strerror(errno));
						break;
					} else
					{
						rc = send(st, buf, rc, 0);//將從檔案中讀到的資料,通過socket傳送到server端,其中rc為從檔案中讀到的資料大小
						if (rc <= 0)//如果傳送失敗,代表TCP連接出錯,迴圈break
						{
							if (rc < 0)
								printf("send failed %s\n", strerror(errno));
							else
								printf("socket disconnect\n");
							break;
						}
					}
				}
			}
		}
	}

	fclose(fd);
	free(buf);

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

int recv_work(int port)//server端socket在port指定的埠上listen,接收來自client傳送的檔案
{
	SOCKET listen_st = socket_create(port);//建立server端socket,在port指定埠listen
	if (listen_st == 0)//如果建立服務端socket失敗,函式返回0
		return 0;
	SOCKET st = socket_accept(listen_st);//如果有client連線到達,socket_accept函式返回client的socket描述符
	if (st == 0)
		return 0;

	char *buf = malloc(BUFSIZE);//建立接收檔案資料緩衝區
	FILE *fd = NULL;

	memset(buf, 0, BUFSIZE);
	size_t rc = recv(st, buf, BUFSIZE, 0);//接收來自client的資料,客戶端第一次要傳送的檔名稱
	if (rc <= 0)
	{
		if (rc < 0)
			printf("recv failed %s\n", strerror(errno));
		else
			printf("socket disconnect\n");

	} else
	{
		printf("receiving %s\n", buf);

		fd = fopen(buf, "wb");//以只寫方式開啟檔案
		if (fd == NULL)
		{
			printf("open %s failed %s\n", buf, strerror(errno));
		} else
		{
			memset(buf, 0, BUFSIZE);
			strcpy(buf, "OK");
			rc = send(st, buf, strlen(buf), 0);//回覆客戶端,同意接收檔案
			if (rc <= 0)
			{
				if (rc < 0)
					printf("send failed %s\n", strerror(errno));
				else
					printf("socket disconnect\n");
			}

			while (1)
			{
				memset(buf, 0, BUFSIZE);
				rc = recv(st, buf, BUFSIZE, 0);//迴圈接收來自client的資料,資料為傳送檔案的內容
				if (rc <= 0)//如果client連線斷開,代表檔案傳遞完成,或者網路意外中斷,迴圈break
				{
					if (rc < 0)
						printf("recv failed %s\n", strerror(errno));
					else
						printf("socket disconnect\n");
					break;
				} else
				{
					fwrite(buf, 1, rc, fd);//將從client端收到的內容寫入檔案
				}
			}
		}
	}

	if (fd)
		fclose(fd);//關閉開啟的檔案
	free(buf);

#ifdef WIN	
	closesocket(st);
	closesocket(listen_st);
	WSACleanup();
#else	
	close(st);
	close(listen_st);
#endif
	return 1;
}

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

int main(int arg, char *args[])
{
	if (arg < 4)//如果引數小於3個,main函式退出
	{
		printf("usage:client host port filename\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)//將第一個引數做為IP地址,第二個引數做為埠號,第三個引數做為要傳送的檔名傳遞給send_work函式
		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("usage:server 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;
}

在window下編譯命令,進入到原始檔所在路徑下,執行: make -f makefile-win
執行: 一:windows接收,linux傳送

二:windows傳送,linux接收