【Scoket编程】篇二

通过篇一,我初步学习了 Socket编程,下面我们继续深入学习。

在篇一中我们首先简单地传输了比较少的字符串,这里我将使用 Socket 传递一个文件。


首先让我们熟悉下 Socket编程的收发API:

1)send() / sendto()

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
send() 只适用于面向连接的协议,sendto() 则适用于面向连接和不连接两种协议。



Description

The send() call may be used only when the socket is in a connected state (so that the intended recipient is known). The only difference between send() and write() is the presence of flags. With a zero flags argument, send() is equivalent to write()

Also, the following call

send(sockfd, buf, len, flags);
is equivalent to
sendto(sockfd, buf, len, flags, NULL, 0);

If sendto() is used on a connection-mode (SOCK_STREAM, SOCK_SEQPACKET) socket, the arguments dest_addr and addrlen are ignored (and the error EISCONN may be returned when they are not NULL and 0), and the error ENOTCONN is returned when the socket was not actually connected. Otherwise, the address of the target is given by dest_addr with addrlen specifying its size. 


Return Value

On success, these calls return the number of characters sent. On error, -1 is returned, and errno is set appropriately.If the message is too long to pass atomically through the underlying protocol, the error EMSGSIZE is returned, and the message is not transmitted.



2)  recv() / recvfrom()

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

和上面相对应的,recv() 只适用于面向连接的协议,与 send() 配对,sendto() 则适用于面向连接和不连接两种协议,和recvfrom() 配对。


All two routines return the length of the message on successful completion. If a message is too long to fit in the supplied buffer, excess bytes may be discarded depending on the type of socket the message is received from.If no messages are available at the socket, the receive calls wait for a message to arrive, unless the socket is nonblocking (see fcntl()), in which case the value -1 is returned and the external variable errno is set to EAGAIN or EWOULDBLOCK. The receive calls normally return any data available, up to the requested amount, rather than waiting for receipt of the full amount requested.



使用 Socket传输文件(TCP实现),代码如下:

服务器端:

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

#define SERVER_PORT 2222
#define BUFF_SIZE 4096

static void receive(int s, struct sockaddr* client, const char* filename);

int main()
{
	int s;
	const char* filename = "receive_file";
	struct sockaddr_in addr_serv, addr_client;
	socklen_t len;

	s = socket(AF_INET, SOCK_STREAM, 0);

	memset(&addr_serv, 0, sizeof(addr_serv));
	addr_serv.sin_family = AF_INET;
	addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
	addr_serv.sin_port = htons(SERVER_PORT);

	assert(bind(s, (struct sockaddr*)&addr_serv, sizeof(addr_serv)) != -1);
	assert(listen(s, 5) != -1);

	printf("waiting...\n");
	int connfd = accept(s, (struct sockaddr*)&addr_client, &len);
	if(connfd == -1)
		printf("accept error\n");
	printf("come from: %s:%d\n", inet_ntoa(addr_client.sin_addr), ntohs(addr_client.sin_port));
	receive(connfd, (struct sockaddr*) &addr_client, filename);
	
	close(connfd);
	return 0;
}

static void receive(int s, struct sockaddr* client, const char* filename)
{
	FILE *dest = fopen("receive file", "w");
	//int dest = open("receive file", O_RDWR | O_CREAT, 0644);            //使用文件描述符读写文件,这里的 0664 表示新建文件的用
	int size = 0, n;                                                      //户权限,开始没有设置,导致生成的文件没有权限查看o(╯□╰)o

	
	if (dest == NULL)
	//if(dest == -1)
		printf("Open file %s failure\n", filename);
	else
		printf("Open file %s success\n", filename);
	
	char* buff = new char[BUFF_SIZE];
	socklen_t len;

	while(1)
	{
		len = sizeof(*client);
		n = recvfrom(s, buff, BUFF_SIZE, 0, client, &len);
		if (n < 1)
		{	
			printf("Completion\n%d bytes received in all.\n", size);
			break;
		}
		else
		{	
			size += n;
			fwrite(buff, 1, n, dest);
			//write(dest, buff, n);
		}
	}
	fclose(dest);
	//close(dest);
	delete[] buff;
}


客户端:

#include <limits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

#define SERVER_PORT 2222
#define BUFFER_SIZE 4096

static void send_file(int s, char* filename, struct sockaddr* to);

int main()
{
	int s;
	char* filename = new char[NAME_MAX];
	scanf("%s", filename);
	printf("%s\n", filename);

	struct sockaddr_in addr_serv;

	// TCP
	s = socket(AF_INET, SOCK_STREAM, 0);
	assert(s != -1);
	// initialization
	const char* server_IP = "127.0.0.1";
	memset(&addr_serv, 0, sizeof(addr_serv));
	addr_serv.sin_family = AF_INET;
	addr_serv.sin_addr.s_addr = inet_addr(server_IP);
	addr_serv.sin_port = htons(SERVER_PORT);

	send_file(s, filename, (struct sockaddr*) &addr_serv);

	close(s);
	return 0;
}

static void send_file(int s, char* filename, struct sockaddr* to)
{
	int size = 0;
	ssize_t n;
	char* buff = new char[BUFFER_SIZE];
	FILE *source = fopen(filename, "r");
	//int source = open(filename, O_RDONLY);

	struct sockaddr_in from;
	socklen_t len = sizeof(*to);

	if (source == NULL)
	//if(source == -1)
		printf("Open file %s failure\n", filename);
	else
		printf("Open file %s success\n", filename);
	assert(connect(s, to, len) != -1);

	while(1)
	{
		n = fread(buff, 1, BUFFER_SIZE, source);
		//n = read(source, buff, BUFFER_SIZE);
		if (n < 1)
		{
			printf("%d bytes transmiited in all.\n", size);
			break;
		}
		else
		{
			size += n;
			sendto(s, buff, n, 0, to, len);
		}
	}
	fclose(buff);
	//close(buff);
	delete[] buff;
}



以上我们使用了两种方法读取文件:

1)fopen() / fread() / fwrite()

2)open() / read() / write()

两种方法在读取大小上描述方法不同,需要格外注意。拿 read() 和 fread() 对比如下:

ssize_t read(int fd, void *buf, size_t count);
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.


size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
The function fread() reads nmemb items of data, each size bytes long, from the stream pointed to by stream, storing them at the location given by ptr.


即:

fread() 读取大小为 size * nmemb 字节大小,read() 读取 count 字节大小。注意区分。还要知道的是对文件描述符进行操作的函数都采用后者,如 write() / read(), send() / recv(), sendto() / recvfrom() 。



刚好在此总结一下:fread,read的区别。(当然这两个分别代表了操作文件系统的两套不同的函数)

read系列:open,read,write等;

fread系列:fopen,fread,fwrite等。

相同点:都可以对文件进行二进制流读写。

最基本的区别是:(read)系列是UNIX 中的系统调用,提供给程序员操作文件的接口;而(fread)系列则是C语言提供的读取文件的函数库,自然这个函数库(ANSI)的实现是以对应的系统调用为基础的。


那么为什么C语言的函数库需要向我们提供这样的包装,而其他的系统调用(像进程、线程管理与通信等等)没有这样的包装呢?
这当然是有原因的,也就是我们常说的有缓冲读写无缓冲读写

关于缓冲问题,以后再看。



以上我们是使用 TCP 完成的,如果我们想用 UDP 实现呢?

我们要知道 TCP 和 UDP 的基本区别:

1)TCP 是面向连接的传输协议,建立连接时要经过三次握手,断开连接时要经过四次握手,中间传输数据时也要回复ACK包确认,多种机制保证了数据能够正确到达,不会丢失或出错。

2)UDP 是非连接的传输协议,没有建立连接和断开连接的过程,它只是简单地把数据丢到网络中,也不需要ACK包确认。


UDP中的服务器端和客户端没有连接

UDP不像TCP,无需在连接状态下交换数据,因此基于UDP的服务器端和客户端也无需经过连接过程。也就是说,不必调用 listen() 和 accept() 函数。UDP中只有创建套接字的过程和数据交换的过程。


UDP服务器端和客户端均只需1个套接字

TCP中,套接字是一对一的关系。如要向10个客户端提供服务,那么除了负责监听的套接字外,还需要创建10套接字。但在UDP中,不管是服务器端还是客户端都只需要1个套接字。之前解释UDP原理的时候举了邮寄包裹的例子,负责邮寄包裹的快递公司可以比喻为UDP套接字,只要有1个快递公司,就可以通过它向任意地址邮寄包裹。同样,只需1个UDP套接字就可以向任意主机传送数据。


基于UDP的接收和发送函数

创建好TCP套接字后,传输数据时无需再添加地址信息,因为TCP套接字将保持与对方套接字的连接。换言之,TCP套接字知道目标地址信息。但UDP套接字不会保持连接状态,每次传输数据都要添加目标地址信息,这相当于在邮寄包裹前填写收件人地址。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值