tcpcopyclient.c
#include <stdio.h> //printf,sprintf,perror 相关函数在此声明
#include <string.h> //memset 相关函数在此声明
#include <unistd.h> //read,close 相关函数在此声明
#include <arpa/inet.h> //socket,sockaddr_in,AF_INET,SOCK_STREAM,htons,inet_addr,connect,send,recv 相关函数和宏在此声明和定义
#include <fcntl.h> //open,O_RDONLY 相关函数和宏在此声明和定义
#define BUF_SIZE 1024
#define PORT 9000
int main(int argc,char *argv[])
{
struct sockaddr_in server_sai;
int sfd=0,res=-1,recvbytes=0,sendbytes=0,readbytes=0,fa=0;
char buf[BUF_SIZE],buf2[5]={0};
char *filename=argv[2]; //进行变量的定义和初始化
if(argc != 3)
{
printf("error number of argc:%dn",argc);
return res;
}
if (-1==(fa=open(argv[2],O_RDONLY,0644))) //将最后一个参数作为文件名,打开文件
{
printf("cannot open file:%sn",filename);
return res;
}
if(-1 == (sfd=socket(AF_INET,SOCK_STREAM,0))) //创建一个IPV4的TCP socket
{
perror("socket");
return res;
}
server_sai.sin_family=AF_INET; //IPV4 协议族
server_sai.sin_port=htons(PORT); //9000端口
server_sai.sin_addr.s_addr=inet_addr(argv[1]); //使用第一个参数作为IP地址
memset(&(server_sai.sin_zero),0,sizeof(server_sai.sin_zero)); //将结构体剩余部分填零
if (-1 == connect(sfd,(struct sockaddr *)&server_sai,sizeof(struct sockaddr))) //使用sfd进行连接
{
perror("connect");
return res;
}
memset(buf,0,sizeof(buf)); //将buf清零
do
{
if(-1 == (readbytes=read(fa,buf,sizeof(buf)))) //从指定文件中读取数据写到buf中
{
printf("read error on:%sn",filename);
return res;
}
if (-1 == (sendbytes=send(sfd,buf,readbytes,0))) //将buf中的数据写到远端
{
perror("send");
return res;
}
}while(readbytes == sizeof(buf) ); //如果读取的数据不再是一整块,就意味着已经读完,随即跳出循环
if (-1 == (recvbytes=recv(sfd,buf2,5,0))) //从远端读取数据到buf2中
{
perror("send");
return res;
}
printf("%d -->%sn",recvbytes,buf2); //将接收到的字节数和数据内容打印出来
close(sfd);
close(fa); //进行清理工作,关闭描述符
res=0;
return res;
}
编译执行
代码语言:javascript复制emacs@ubuntu:~/c$ alias gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc tcpcopyserver.x tcpcopyserver.c ; gtc tcpcopyclient.x tcpcopyclient.c
emacs@ubuntu:~/c$
此时系统中并没有开放9000端口
代码语言:javascript复制emacs@ubuntu:~/c$ netstat -ant | grep 9000
emacs@ubuntu:~/c$
运行服务端
代码语言:javascript复制emacs@ubuntu:~/c$ ./tcpcopyserver.x
Listening...
此时系统中多了一个9000端口
代码语言:javascript复制emacs@ubuntu:~/c$ netstat -ant | grep 9000
tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN
emacs@ubuntu:~/c$
服务端也并没有 /tmp/x.download
这个文件
emacs@ubuntu:~/c$ ll /tmp/x.download
ls: 无法访问/tmp/x.download: 没有那个文件或目录
emacs@ubuntu:~/c$
运行客户端,会立刻返回
代码语言:javascript复制emacs@ubuntu:~/c$ du -sh 4.png
8.6M 4.png
emacs@ubuntu:~/c$
emacs@ubuntu:~/c$ ./tcpcopyclient.x 127.0.0.1 4.png
2 -->OK
emacs@ubuntu:~/c$
服务端会打印信息并且返回,对比两个文件也没有差异
代码语言:javascript复制emacs@ubuntu:~/c$ ./tcpcopyserver.x
Listening...
emacs@ubuntu:~/c$
emacs@ubuntu:~/c$ diff /tmp/x.download 4.png
emacs@ubuntu:~/c$
编译执行过程中没有报错,从结果来看,符合预期
sockaddr 结构体
bits/socket.h
中有关于 sockaddr 结构体的定义
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
in_addr 结构体
netinet/in.h
中有关于 in_addr 结构体的定义
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
sockaddr_in 结构体
netinet/in.h
中有关于 sockaddr_in 结构体的定义
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
从 sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]
中可以看出
这个字段,是为填补与 sockaddr
结构体的长度差
二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别
socket
sys/socket.h
中有关于 socket 函数的声明
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
extern int socket (int __domain, int __type, int __protocol) __THROW;
用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作
__domain
即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址
__type
指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
__protocol
指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
Note: 并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议
AF_INET 和 SOCK_STREAM 宏定义
bits/socket.h
中有关于 AF_INET 和 SOCK_STREAM 的宏定义
emacs@ubuntu:/usr/include$ grep AF_INET bits/socket.h
#define AF_INET PF_INET
#define AF_INET6 PF_INET6
emacs@ubuntu:/usr/include$ grep PF_INET bits/socket.h
#define PF_INET 2 /* IP protocol family. */
#define PF_INET6 10 /* IP version 6. */
#define AF_INET PF_INET
#define AF_INET6 PF_INET6
emacs@ubuntu:/usr/include$ grep SOCK_STREAM bits/socket.h
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
#define SOCK_STREAM SOCK_STREAM
emacs@ubuntu:/usr/include$
使用此方法可以获取其它想要的宏定义
htons
netinet/in.h
中有关于 htons 的定义
/* Functions to convert between host and network byte order.
Please note that these functions normally take `unsigned long int' or
`unsigned short int' values as arguments and also return them. But
this was a short-sighted decision since on different systems the types
may have different representations but the values are always the same. */
extern uint32_t ntohl (uint32_t __netlong) __THROW __attribute__ ((__const__));
extern uint16_t ntohs (uint16_t __netshort)
__THROW __attribute__ ((__const__));
extern uint32_t htonl (uint32_t __hostlong)
__THROW __attribute__ ((__const__));
extern uint16_t htons (uint16_t __hostshort)
__THROW __attribute__ ((__const__));