socket编程——TCP/UDP数据传输
时间:2010-07-15 来源:ubuntuer
socket()——生成socket句柄
bind()——将本端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上
bind ()的作用:
作为client端发包时,bind上的是源地址和源端口
作为server端收包时,是lisen的地址和listen的端口
connect()——将对端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上
connect()的作用
TCP下发起连接——在STREAM下(TCP),connect()会企图建立连接attempt to establish a connection
UDP下设对端地址和端口——在DAGRAM下(UDP),connect()的作用只是设peer address/port,而不建立连接
实验证明:TCP情况下(SOCK_STREAM),connect() bind(),二者都不能少
实验证明:UDP情况下(SOCK_DGRAM),bind()可以不要,connect()也可以被sendto()代替
例1:TCP下将bind()函数注释掉,无法建立连接
例2:将上例改成SOCK_DGRAM(UDP),就好了。
UDP不需要BIND (其实连CONNECT也可省略,就剩SENDTO即可),TCP则BIND,CONNECT缺一不可
UDP(SOCK_DGRAM)下,sendto()函数和connect()函数冲突,用sendto,就不能用connect()
发UDP包100个,可是每发1个,就停止了,出错。
解决:删掉connect(),直接用sendto();
UDP sendto发包,尽管配了connect()系统也不报错,但connect函数会使UDP包无法连续发
UDP到底怎么发包?
总结TCP,UDP发包的调用函数过程
sizeof(struct sockaddr) 的含义:connect() 将不会将多余的字节给 socket
关于发包的随机绑定源端口,TCP和UDP做法不同
TCP下,bind(),可以通过 cliaddr.sin_port = htons(0);实现TCP自动绑定源端口
UDP下,sendto自动绑定源端口,或connect(),自动绑定,再send()
int shutdown(int sockfd, int how); 关闭通讯
how 的值是下面的其中之 一:
0 – 不允许接受
1 – 不允许发送
2 – 不允许发送和接受(和 close() 一样)
server端 的处理过程
listen()
accept()
accept 全过程
新的一个在准备发送 (send()) 和接收 ( recv()) 数据
bind 的地址和端口
一个完整的server 端bind+listen+accept
只想让一个连接进来——accpet()后,使用 close() 去关闭原来的socket句柄
send()
通常用于tcp的stream,必须与bind和connect配合
recv()
sendto()
用于udp的DGRAM不用bind()和connect()函数的情况下
sendto,recvfrom与send,recv不同之处,要加上struct sockaddr参数,即替代了bind()的作用
例1:发一串字符的UDP包
例2:发一个正经的包(一个cisco netflow包)
recvfrom()
UDP不需要listen(),必须直接recvfrom(),有listen会报错
改成TCP
收发会不会存在“对不齐”的问题?
1。对齐问题,sizeof会自动解决
2。只要收端和发端struct定义相同,就不会有问题。
errno和strerror(errno)
系统函数返回值是-1,这时一个系统全局变量errno就会被赋值
立刻执行strerror(errno),会返回一个错误描述字符串
errno
errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之它。
主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误
perror print a system error message
#include <sys/types.h>; #include <sys/socket.h>; int socket(int domain, int type, int protocol); domain : "AF_INET" type : SOCK_STREAM(TCP), SOCK_DGRAM (UDP) protocol:0 返回值:socket句柄(整型); (注意:有很多种 domain、type,请看 socket() 的 man帮助。 另一个方式去得到 protocol。同 时请查阅 getprotobyname() 的 man 帮助。) |
int clifd; if ((clifd = socket(AF_INET,SOCK_DGRAM(或者SOCK_STREAM),0)) < 0) { printf("create socket error!\n"); exit(1); } … close(clifd); |
bind()——将本端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上
int bind(int sockfd, struct sockaddr *my_addr, int addrlen); |
sockfd = socket(AF_INET, SOCK_STREAM, 0); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(MYPORT); my_addr.sin_addr.s_addr = inet_addr("132.241.5.10"); bzero(&(my_addr.sin_zero)); bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) |
作为client端发包时,bind上的是源地址和源端口
作为server端收包时,是lisen的地址和listen的端口
connect()——将对端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); |
connect(sockfd, (struct sockaddr *) &dest_addr, sizeof(struct sockaddr)); |
TCP下发起连接——在STREAM下(TCP),connect()会企图建立连接attempt to establish a connection
UDP下设对端地址和端口——在DAGRAM下(UDP),connect()的作用只是设peer address/port,而不建立连接
实验证明:TCP情况下(SOCK_STREAM),connect() bind(),二者都不能少
实验证明:UDP情况下(SOCK_DGRAM),bind()可以不要,connect()也可以被sendto()代替
例1:TCP下将bind()函数注释掉,无法建立连接
if ((clifd = socket(AF_INET,SOCK_STREAM,0)) < 0) { printf("create socket error!\n"); exit(1); } bzero(&cliaddr,sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(0); cliaddr.sin_addr.s_addr = htons(INADDR_ANY); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_aton(argv[1],&servaddr.sin_addr); servaddr.sin_port = htons(SERVER_PORT); if (connect(clifd,(struct sockaddr*)&servaddr, socklen) < 0) { printf("can't connect to %s!\n",argv[1]); exit(1); } |
[root@nm socket]# ./client 10.4.3.55 can't connect to 10.4.3.55! |
if ((clifd = socket(AF_INET,SOCK_DGRAM,0)) < 0) { printf("create socket error!\n"); exit(1); } |
[root@nm socket]# ./client 10.4.1.105 sent 14 bytes to 10.4.1.105 |
UDP(SOCK_DGRAM)下,sendto()函数和connect()函数冲突,用sendto,就不能用connect()
发UDP包100个,可是每发1个,就停止了,出错。
if ((clifd = socket(AF_INET,SOCK_DGRAM,0)) < 0) { printf("create socket error!\n"); exit(1); } bzero(&cliaddr,sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(0); cliaddr.sin_addr.s_addr = htons(INADDR_ANY); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_aton(argv[1],&servaddr.sin_addr); servaddr.sin_port = htons(SERVER_PORT); if (connect(clifd,(struct sockaddr*)&servaddr, socklen) < 0) { printf("can't connect to %s!\n",argv[1]); exit(1); } strcpy(message,"this is a test"); for(i=0;i<100;i++){ if ((numbytes=sendto(clifd, message, strlen(message), 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) == -1) { printf("error send message %s!",message); exit(1); } printf("sent %d bytes %s to %s\n",numbytes,message,inet_ntoa(servaddr.sin_addr)); } |
[root@nm socket]# gcc -o client client.c [root@nm socket]# ./client 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 为什么不继续传了? |
结果: [root@nm socket]# ./client 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 sent 14 bytes this is a test to 10.4.1.105 |
UDP到底怎么发包?
- 或者直接sendto();
- 或者conncet() + send() 代替 sendto()
总结TCP,UDP发包的调用函数过程
TCP发包 | UDP发包 |
socket() bind() connect() send() close(clifd) |
socket() sendto() close(clifd) |
socket() connect() send() close(clifd) |
sizeof(struct sockaddr) 的含义:connect() 将不会将多余的字节给 socket
关于发包的随机绑定源端口,TCP和UDP做法不同
TCP下,bind(),可以通过 cliaddr.sin_port = htons(0);实现TCP自动绑定源端口
UDP下,sendto自动绑定源端口,或connect(),自动绑定,再send()
int shutdown(int sockfd, int how); 关闭通讯
how 的值是下面的其中之 一:
0 – 不允许接受
1 – 不允许发送
2 – 不允许发送和接受(和 close() 一样)
server端 的处理过程
- 1。bind() 捆绑本地listen 端口号
- 2。listen() 开始听
- 3。accept() 处理
listen()
int listen(int sockfd, int backlog); |
int backlog: 队列长度,队列中允许的连接数目 进入的连接在队列中会一直等待直到被接受 accept() |
accept()
int accept(int sockfd, sockaddr_in *addr, int *sin_size); |
注意,是sockaddr_in,所以不需要强制转化成sockaddr类型了 int *sin_size:是 sizeof(struct sockaddr_in)的指针化 返回值:新句柄 以后send()和 recv()中应该使用accept()产生的新的socket 句柄 new_fd, 原有句柄sockfd继续用于listen |
accept 全过程
- 远端client通过一个server端的listen端口连接进来
- 它的connection将加入到等待接受的队列中。
- accept() 告诉它你有空闲的连接。
- accept()将返回一个新的socket句柄!
- 这样你就有两个socket了:
新的一个在准备发送 (send()) 和接收 ( recv()) 数据
bind 的地址和端口
- 发包时,是源地址和源端口
- 收包时,是lisen的地址和listen的端口(也是源地址和端口)
一个完整的server 端bind+listen+accept
#include <string.h>; #include <sys/socket.h>; #include <sys/types.h>; #define MYPORT 3490 #define BACKLOG 10 main() { int sockfd, new_fd; struct sockaddr_in my_addr,their_addr; int sin_size; sockfd = socket(AF_INET, SOCK_STREAM, 0); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(MYPORT); my_addr.sin_addr.s_addr = INADDR_ANY; 任意地址,即listen本机所有接口 bzero(&(my_addr.sin_zero)); bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); listen(sockfd, BACKLOG); 开始listen in_size = sizeof(struct sockaddr_in); 需要单独一个sizeof变量,因为accept需要传的是指针 new_fd = accept(sockfd, &their_addr, &sin_size); 注意这里accept的参数their_addr没经过任何赋值,是空的 以后send()和 recv()中你应该使用accept()产生的新的socket 句柄 new_fd |
只想让一个连接进来——accpet()后,使用 close() 去关闭原来的socket句柄
int sockfd, new_fd; sockfd = socket(AF_INET, SOCK_STREAM, 0); new_fd = accept(sockfd, &their_addr, &sin_size); close(sockdf); |
send()
通常用于tcp的stream,必须与bind和connect配合
int send(int sockfd, const void *msg, int len, int flags); |
const void *msg:要发的信息的指针 msg可以是任何类型的指针,比如一个字符串指针,或者一个struct指针(一般packet都是一个struct) int len :要发的信息的长度 flags: 设置为0。 返回值是实际发送的数据的字节数--它可能小于你要求发送的数目 |
char *msg = "hello world"; int len, bytes_sent; len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); |
recv()
int recv(int sockfd, void *buf, int len, unsigned int flags); |
void *buf : 存接收的信息的缓冲区的地址(指针) int len: 缓冲区的最大max长度 (如果buffer是一个数组char s[30],则len就是30) flags: 设置为0 |
char pack[100]; recv(sockfd,&pack,sizeof(pack),0); |
sendto()
用于udp的DGRAM不用bind()和connect()函数的情况下
sendto,recvfrom与send,recv不同之处,要加上struct sockaddr参数,即替代了bind()的作用
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen); |
const void *msg:要发的信息的指针 msg可以是任何类型的指针,比如一个字符串指针,或者一个struct指针(一般packet都是一个struct) int len:要发的信息的长度 flags: 设置为00 const struct sockaddr *to: 要强制地址转换 int tolen: sizeof(struct sockaddr) 返回值:实际发出的字节数 |
if ((numbytes=sendto(clifd, message, strlen(message), 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) == -1) { printf("error send message %s!",message); exit(1); } |
if ((numbytes=sendto(clifd, &pkts,24+48*FLOWCOUNT, 0, (struct sockaddr *)&servaddr, socklen)) == -1) { printf("error send !\n"); printf("Wait Error:%s\n",strerror(errno)); exit(1); } |
recvfrom()
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen); |
void *buf : 存接收的信息的缓冲区的地址(指针) int len: 缓冲区的最大max长度 (如果buffer是一个数组char s[30],则len就是30) flags: 设置为0 const struct sockaddr *from: 它的内容是接收到的包源IP 地址和源端口信息 要强制地址转换 int fromlen: sizeof(struct sockaddr) 的指针 |
UDP不需要listen(),必须直接recvfrom(),有listen会报错
[root@nm server]# ./server call listen failure! -1 |
if ((servfd = socket(AF_INET,SOCK_STREAM,0)) < 0) |
[root@nm server]# ./server |
收发会不会存在“对不齐”的问题?
1。对齐问题,sizeof会自动解决
2。只要收端和发端struct定义相同,就不会有问题。
errno和strerror(errno)
系统函数返回值是-1,这时一个系统全局变量errno就会被赋值
立刻执行strerror(errno),会返回一个错误描述字符串
if ((numbytes=sendto(clifd, (char *)&pkts,24+48*FLOWCOUNT, 0, (struct sockaddr *)&servaddr, socklen)) == -1) { printf("Wait Error:%s\n",strerror(errno)) } |
errno
errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之它。
主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误
#include <errno.h> #include <math.h> #include <stdio.h> int main(void) { errno = 0; 使用errno之前,我们最好将其设置为0(正常值) if (NULL == fopen("d:\\1.txt", "rb")) { printf("%d", errno); } |
perror print a system error message
if (serverSocket = socket(PF_INET, SOCK_STREAM,IPPROTO_TCP); ) { perror("socket()"); exit(1); } |
相关阅读 更多 +