網絡編程1----TCP通信

一 理論

網絡編程(各種編程語言幾乎都支持同一種協議,可進行各種設備間的相互通信)TCP/IP




tcp使用的過程,
client端,主動連接方叫client。
server端,被動接收方叫server。

1、TCP先連接(三次握手)
client > server:發送SYN(同步信號),起始編號爲 a
server > client:回覆SYN ,起始編號爲b,包含ack(a + 1)
client > server ack (b + 1)

2、連接通了,雙方可以互相收發消息

3、斷開連接
client > server:FIN m
server > client :ack m +1
server > client:FIN n
client > server :ack n + 1












二 代碼一:單方通信


客戶端代碼:

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

int main()
{
	int st = socket(AF_INET, SOCK_STREAM, 0);//初始化socket
	struct sockaddr_in addr;//定義一個IP地址的結構
	memset(&addr, 0, sizeof(addr));//將IP地址初始化爲0
	addr.sin_family = AF_INET;//設置結構地址類型爲TCP/IP地址
	addr.sin_port = htons(8080);//指定一個端口號爲8080,其中htons是將short類型從host字節類型轉到net字節類型的轉化
	addr.sin_addr.s_addr = inet_addr("10.170.40.27");//字符串IP地址轉化爲整數

	if (connect(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)//連接
	{
		printf("connect failed %s\n", strerror(errno));
		return -1;
	}

	char s[1024];
	memset(s, 0, sizeof(s));
	strcpy(s, "I'm Apple Lin.");

	if (send(st, s, sizeof(s), 0) == -1)//發送數據
	{
		printf("Sending faled %s\n", strerror(errno));
		return -1;
	}
	printf("sending success!\n");

	close(st);//關閉套接字

	return 0;
}


服務器端代碼:

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

int main()
{
	int st = socket(AF_INET, SOCK_STREAM, 0);

	int on = 1;
	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
	{
		printf("setsockopt failed %s\n", strerror(errno));
		return -1;
	}
	
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));//將IP地址初始化爲0
	addr.sin_family = AF_INET;//設置結構地址類型爲TCP/IP地址
	addr.sin_port = htons(8080);//指定一個端口號爲8080,其中htons是將short類型從host字節類型轉到net字節類型的轉化
	addr.sin_addr.s_addr = htonl(INADDR_ANY);//這個server上所有的IP地址

	if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	{
		printf("bind failed %s\n", strerror(errno));
		return - 1;
	}

	if (listen(st, 20) == -1)//server開始監聽,同時最多監聽20個客戶
	{
		printf("listen failed %s\n", strerror(errno));
		return -1;
	}
	
	printf("statring server...\n");

	char s[1024];
	int client_st = 0;//client端的socket
	struct sockaddr_in client_addr;//客戶端的IP地址
//	void *p = &client_addr;
	int i=0;
	for (i = 0; i < 5; ++i)//指定5次
	{
		memset(&client_addr, 0, sizeof(client_addr));
		socklen_t len = sizeof(client_addr);
		client_st = accept(st, (struct sockaddr *)&client_addr, &len);
		if (client_st == -1)
		{
			printf("client failed %s\n", strerror(errno));
			return -1;
		
		}

		
		while(1)
		{
			memset(s, 0, sizeof(s));
			int rc = recv(client_st, s, sizeof(s), 0);
			if(rc > 0)
			{
				printf("recv is %s\n", s);
			}
			else if (rc == 0)
			{
				printf("client socket closed\n");
				break;
			}
			else
			{
				printf("recv failed %s\n", strerror(errno));
				break;
			}

		}
		close(client_st);
	}
	close(st);
	return 0;
}

makefile文件

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

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

運行結果:



三 雙方單線程阻塞式通信


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

int main(int argc, char *argv[])
{
	if (argc < 3)
	{
		printf("Input argument like : client | IP | port\n");
		return;
	}

	int pot = atoi(argv[2]);

	int st = socket(AF_INET, SOCK_STREAM, 0);//初始化socket
	struct sockaddr_in addr;//定義一個IP地址的結構
	memset(&addr, 0, sizeof(addr));//將IP地址初始化爲0
	addr.sin_family = AF_INET;//設置結構地址類型爲TCP/IP地址
	addr.sin_port = htons(pot);//指定一個端口號爲8080,其中htons是將short類型從host字節類型轉到net字節類型的轉化
	addr.sin_addr.s_addr = inet_addr(argv[1]);//字符串IP地址轉化爲整數

	if (connect(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)//連接
	{
		printf("connect failed %s\n", strerror(errno));
		return -1;
	}

	char s[1024];
	while (1)
	{
		memset(s, 0, sizeof(s));
		read(STDIN_FILENO, s, sizeof(s));

		if (send(st, s, sizeof(s), 0) == -1)//發送數據
		{
			printf("Sending faled %s\n", strerror(errno));
			return -1;
		}
		printf("send success!\n");

		//接受來自server的消息
		memset(s, 0, sizeof(s));
		if (recv(st, s, sizeof(s), 0) > 0)//如果接收到消息,就打印該消息,否則退出通話
			printf("Server say: %s\n", s);
		else
			break;
	}
	close(st);//關閉套接字

	return 0;
}

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

int main(int argc, char *argv[])
{
	if (argc < 2)
	{
		printf("Input argument like this: server | port\n");
		return -1;
	}

	int pot = atoi(argv[1]);

	int st = socket(AF_INET, SOCK_STREAM, 0);

	int on = 1;
	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)//地址重用
	{
		printf("setsockopt failed %s\n", strerror(errno));
		return -1;
	}
	
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));//將IP地址初始化爲0
	addr.sin_family = AF_INET;//設置結構地址類型爲TCP/IP地址
	addr.sin_port = htons(pot);//指定一個端口號爲8080,其中htons是將short類型從host字節類型轉到net字節類型的轉化
	addr.sin_addr.s_addr = htonl(INADDR_ANY);//這個server上所有的IP地址

	if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	{
		printf("bind failed %s\n", strerror(errno));
		return - 1;
	}

	if (listen(st, 20) == -1)//server開始監聽,同時最多監聽20個客戶
	{
		printf("listen failed %s\n", strerror(errno));
		return -1;
	}
	
	printf("statring server...\n");

	char s[1024];
	int client_st = 0;//client端的socket
	struct sockaddr_in client_addr;//客戶端的IP地址
//	void *p = &client_addr;
	while (1)
	{
		memset(&client_addr, 0, sizeof(client_addr));
		socklen_t len = sizeof(client_addr);
		client_st = accept(st, (struct sockaddr *)&client_addr, &len);//向客戶端回覆消息
		if (client_st == -1)
		{
			printf("client failed %s\n", strerror(errno));
			return -1;
		
		}

		while(1)
		{
			memset(s, 0, sizeof(s));
			int rc = recv(client_st, s, sizeof(s), 0);
			if(rc > 0)
			{
				printf("%s say:  %s\n", inet_ntoa(client_addr.sin_addr), s);//inet_ntoa將IP地址轉化爲字符串
				memset(s, 0, sizeof(s));
				read(STDIN_FILENO, s, sizeof(s));
				send(client_st, s, strlen(s), 0);
				printf("send success!\n");
			}
			else if (rc == 0)
			{
				printf("client socket closed\n");
				break;
			}
			else
			{
				printf("recv failed %s\n", strerror(errno));
				break;
			}

		}
		close(client_st);
	}
	close(st);
	return 0;
}

運行結果:


四 增加多線程且限制用戶數量爲1


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

void *recvsocket(void *arg)
{
	int st = *(int *)arg;
	char s[1024];

	while (1)
	{
		memset(s, 0, sizeof(s));
		int rv = recv(st, s, sizeof(s), 0);
		if ( rv <= 0)
		{
			printf("recv failed!\n");
			break;
		}
		printf("Message: %s\n", s);
	}
	return NULL;
}

void *sendsocket(void *arg)
{
	int st = *(int *)arg;
	char s[1024];
	while (1)
	{
		memset(s, 0, sizeof(s));
		read(STDIN_FILENO, s, sizeof(s));
		send(st, s, strlen(s), 0);
	}
	return NULL;
}

int main(int argc, char *argv[])
{
	if (argc < 3)
	{
		printf("Input argument like : client | IP | port\n");
		return;
	}

	int pot = atoi(argv[2]);

	int st = socket(AF_INET, SOCK_STREAM, 0);//初始化socket
	struct sockaddr_in addr;//定義一個IP地址的結構
	memset(&addr, 0, sizeof(addr));//將IP地址初始化爲0
	addr.sin_family = AF_INET;//設置結構地址類型爲TCP/IP地址
	addr.sin_port = htons(pot);//指定一個端口號爲8080,其中htons是將short類型從host字節類型轉到net字節類型的轉化
	addr.sin_addr.s_addr = inet_addr(argv[1]);//字符串IP地址轉化爲整數

	if (connect(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)//連接
	{
		printf("connect failed %s\n", strerror(errno));
		return -1;
	}
	printf("clinet beging...\n");
	pthread_t thrd1, thrd2;
	pthread_create(&thrd1, NULL, recvsocket, &st);
	pthread_create(&thrd2, NULL, sendsocket, &st);
	pthread_join(thrd1, NULL);
	pthread_join(thrd2, NULL);

	close(st);//關閉套接字

	return 0;
}

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

struct ps
{
	int st;
	pthread_t *thr;
};

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int status = 0;

void *recvsocket(void *arg)//接收client端socket數據的線程
{
	struct ps *p = (struct ps *)arg;
	int st = p->st;
	char s[1024];
	while (1)
	{
		memset(s, 0, sizeof(s));
		int rv = recv(st, s, sizeof(s), 0);
		if (rv <= 0)
		{
			printf("recv failed!\n");
			break;
		}
		printf("Message: %s\n", s);
	}
	pthread_mutex_lock(&mutex);
	status = 0;
	pthread_mutex_unlock(&mutex);
	pthread_cancel(*(p->thr));//被cancel掉的線程內部沒有使用鎖。
	return NULL;
}

void *sendsocket(void *arg)
{
	int st = *(int *)arg;
	char s[1024];
	while (1)
	{
		memset(s, 0, sizeof(s));
		read(STDIN_FILENO, s, sizeof(s));
		send(st, s, strlen(s), 0);
	}
	return NULL;
}

int main(int argc, char *argv[])
{
	if (argc < 2)
	{
		printf("Input argument like this: server | port\n");
		return -1;
	}

	int pot = atoi(argv[1]);
	int st = socket(AF_INET, SOCK_STREAM, 0);

	int on = 1;
	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)//地址重用
	{
		printf("setsockopt failed %s\n", strerror(errno));
		return -1;
	}
	
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));//將IP地址初始化爲0
	addr.sin_family = AF_INET;//設置結構地址類型爲TCP/IP地址
	addr.sin_port = htons(pot);//指定一個端口號爲8080,其中htons是將short類型從host字節類型轉到net字節類型的轉化
	addr.sin_addr.s_addr = htonl(INADDR_ANY);//這個server上所有的IP地址

	if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	{
		printf("bind failed %s\n", strerror(errno));
		return - 1;
	}

	if (listen(st, 20) == -1)//server開始監聽,同時最多監聽20個客戶
	{
		printf("listen failed %s\n", strerror(errno));
		return -1;
	}
	
	printf("statring server...\n");

	char s[1024];
	int client_st = 0;//client端的socket
	struct sockaddr_in client_addr;//客戶端的IP地址
//	void *p = &client_addr;

	pthread_t thrd1, thrd2;

	while (1)
	{
		memset(&client_addr, 0, sizeof(client_addr));
		socklen_t len = sizeof(client_addr);
		client_st = accept(st, (struct sockaddr *)&client_addr, &len);//向客戶端回覆消息

		pthread_mutex_lock(&mutex);//爲全局變量加一個互斥鎖,防止與線程函數同時讀寫變量的衝突
		status++;
		pthread_mutex_unlock(&mutex);//解鎖

		if (status > 1)//代表這是第二個socket連接
		{
			close(client_st);
			continue;
		}

		if (client_st == -1)
		{
			printf("client failed %s\n", strerror(errno));
			return -1;
		
		}
		printf("accept by %s\n", inet_ntoa(client_addr.sin_addr));
		struct ps ps1;
		ps1.st = client_st;
		ps1.thr = &thrd2;
		pthread_create(&thrd1, NULL, recvsocket, &ps1);
		pthread_detach(thrd1);//設置線程爲可分離
		pthread_create(&thrd2, NULL, sendsocket, &client_st);
		pthread_detach(thrd2);//設置線程爲可分離
	}
	close(st);
	return 0;
}

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

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)

運行結果:



五 傳輸文件跨平臺(windows && Linux)


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)//將第一個參數做爲IP地址,第二個參數做爲端口號,第三個參數做爲要發送的文件名傳遞給send_work函數
		printf("%s send success\n", args[3]);
	else
		printf("%s send fail\n", args[3]);

	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_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;
}


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;
}

Linux下的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 $@ -c $< 

clean:
	rm -f $(SERVEROBJS)
	rm -f $(CLIENTOBJS)
	rm -f core*


Windows下的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.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 $@ -c $< 

clean:
	rm -f $(SERVEROBJS)
	rm -f $(CLIENTOBJS)
	rm -f core*


六 Qt客戶端代碼(基於TCP)


TCPDialog.h
#ifndef TCPDIALOG_H
#define TCPDIALOG_H

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QTcpSocket>

class Dialog : public QDialog
{
    Q_OBJECT

public:
    Dialog(QWidget *parent = 0);
    ~Dialog();

private:
    QLabel *label1, *label2;
    QLineEdit *edit1, *edit2;
    QPushButton *btn1;
    QTcpSocket *tcpsocket;
    QString filename;

private slots:
    void on_clicked1();
    void dataReceived();
};

#endif // TCPDIALOG_H

TcpDialog.cpp
#include "TcpDialog.h"
#include <QMessageBox>
#include <QGridLayout>
#include <QByteArray>
#include <QHostAddress>
#include <QFileDialog>

#define BUFSIZE 262144  //256k

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{

    tcpsocket = new QTcpSocket(this);

    setWindowTitle(tr("tcp send file client"));//設置窗口標題
    label1 = new QLabel;
    label1->setText(tr("Destination &IP"));
    label2 = new QLabel;
    label2->setText(tr("Destination &Port"));

    edit1 = new QLineEdit;
    edit2 = new QLineEdit;

    label1->setBuddy(edit1);
    label2->setBuddy(edit2);

    btn1 = new QPushButton(tr("&Send file"));

    QGridLayout *layout1 = new QGridLayout(this);

    layout1->addWidget(label1, 0, 0);//加入label1控件
    layout1->addWidget(edit1, 0, 1);//加入edit1控件
    layout1->addWidget(label2, 1, 0);//加入label2控件
    layout1->addWidget(edit2, 1, 1);//加入edit2控件
    layout1->addWidget(btn1, 2, 0);//加入btn1控件

    connect(btn1, SIGNAL(clicked()), this, SLOT(on_clicked1()));
    connect(tcpsocket, SIGNAL(readyRead()), this, SLOT(dataReceived()));
}

Dialog::~Dialog()
{

}

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] == '/'))//爲了兼容linux與windows的路徑,同時解析/與\分隔的路徑名稱
        {
            break;
        }
    }
    strcpy(name, &filename[i + 1]);
    return;
}

void Dialog::on_clicked1()//當Send File按鈕點擊時,會進入選取文件目錄
{
    filename = QFileDialog::getOpenFileName(this, tr("Open File..."));
    if (!filename.isEmpty())
    {

        tcpsocket->close();
        QHostAddress serverIP;
        serverIP.setAddress(edit1->text());
        tcpsocket->connectToHost(serverIP , edit2->text().toInt());//連接到server端

        char buf[1024];
        memset(buf, 0, sizeof(buf));

        //從完整路徑名中解析出文件名稱,例如:/home/test/abc.txt,解析完成後爲abc.txt
        getfilename(filename.toStdString().data(), buf);
        tcpsocket->write(buf, strlen(buf));//將文件名稱發送到server端
    }
}

void Dialog::dataReceived()//接收socket消息的槽函數
{
    char *buf = (char *)malloc(BUFSIZE);//申請一個緩衝區,存放接收到的文件內容

    while(tcpsocket->bytesAvailable() > 0)
    {
        memset(buf, 0, BUFSIZE);
        tcpsocket->read(buf, BUFSIZE);
    }

    if (strncmp(buf, "OK", 2) == 0)//server端回覆同意接收文件
    {
        FILE *fd = fopen(filename.toStdString().data(), "rb");//以只讀方式打開filename指定的文件
        if (fd)//如果文件成功打開
        {
            while (1)
            {
                memset(buf, 0, BUFSIZE);
                size_t rc = fread(buf, 1, BUFSIZE, fd);//循環讀取文件,直到讀到文件尾,循環break
                if (rc <= 0)
                {
                    break;//文件尾,循環break
                } else
                {
                    tcpsocket->write(buf, rc);//將從文件中讀到的數據,通過socket發送到server端,其中rc爲從文件中讀到的數據大小
                    if (rc <= 0)//如果發送失敗,代表TCP連接出錯,循環break
                    {
                        break;
                    }
                }
            }
        }
        fclose(fd);
    }

    tcpsocket->close();//文件發送完成後關閉socket
    free(buf);
}



main.cpp
#include "TcpDialog.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();

    return a.exec();
}

運行結果:



因爲他們都遵從同一種協議,所以可以跨平臺,跨編程語言,只要協議相同即可!!!








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