TCP和UDP以及一些通讯要素可以学习这个的文章:http://t.csdn.cn/lWAOQ
TCP与UDP的区别:
TCP协议:
使用TCP协议前,须先建立TCP连接,形成传输数据通道
传输前,采用“三次握手”方式,点对点通信,是可靠的
TCP协议进行通信的两个应用进程:客户端、服务端。
在连接中可进行大数据量的传输
传输完毕,需释放已建立的连接,效率低
UDP协议:
将数据、源、目的封装成数据包,不需要建立连接
每个数据报的大小限制在64K内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送
发送数据结束时无需释放资源,开销小,速度快
字节序:
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
我们可以将其根据其存储时从低位开始还是从高位开始分为两种
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000&4001&4002&4003
LE 04 03 02 01
BE 01 02 03 04
注意:网络字节序=大端字节序(BE)
x86系列CPU都是little-endian(LE)的字节序
字节序转换htonl、htons、ntohl、ntohs
函数原型:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
参数:
hostlong:主机字节序的长整型数据
hostshort:主机字节序的短整型数据
netlong: 网络字节序的长整型数据
netshort:网络字节序的短整型数据
返回值:
对应的字节序数据
socket编程步骤:
相关API
创建套接字
返回值:成功 待连接套接字(非负描述符)
失败 -1
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数1:域
AF_INET/PF_INET: 网际协议
一般用:AF_INET(IPv4) AF_INET6(IPv6)
AF_UNIX/PF_UNIX:本地协议,可写AF_LOCAL/PF_LOCAL
参数2:SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
参数3:协议,一般为 0
注意:在网际协议中,选择流式套接字就代表 TCP 协议,选择数据包套接字就代表 UDP 协议,第三个参数 protocol 一般都不用
绑定套接字与网络地址
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:待连接套接字
addr:包含本地地址(IP+PORT)的通用地址结构体的指针
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
通用地址结构体的定义:
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
特殊地址结构体 —— IPv4 地址结构体:
struct sockaddr_in
{
u_short sin_family;// 地址族
u_short sin_port;// 端口
struct in_addr sin_addr;// IPV4 地址
char sin_zero[8];
};
struct in_addr
{
in_addr_t s_addr;// 无符号 32 位网络地址
};
特殊地址结构体 ——UNIX 域地址结构体:
struct sockaddr_un
{
u_short sun_family;// 地址族
char sun_path[108];// 套接字文件路径
};
连接对端监听套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:客户端套接字描述符
addr:包含服务器IP地址和端口号的套接字地址结构
addrlen:套接字地址结构体大小
返回值:
成功:0
失败:-1
调用connect前不必非得调用bind,如果没有bind,内核会确定源IP并选择一个临时端口作为源端口
如果是TCP套接字,调用connect将激发TCP三路握手过程,函数会阻塞进程,直到成功或出错才返回。出错可能有下列情况:
返回ETIMEDOUT错误:以4.4BSD为例,内核会先发送一个SYN,若无响应则等待6s后再发送一个,若仍无响应则等待24s后再发送一个。若总共等待了75s后仍未收到响应则返回该错误
返回ECONNREFUSED错误:服务器对客户响应一个RST,表明服务器在客户指定的端口上没有进程在等待与之连接(除此之外,产生RST还有两个条件:1)TCP想取消一个已有连接;2)TCP接收到一个根本不存在的连接上的分节)
返回EHOSTUNREACH或ENETUNREACH错误:客户发出的SYN在中间的某个路由引发一个“目的地
不可达”ICMP错误,内核会报错该消息,并按情况1中的间隔继续发送SYN,若在规定时间内仍未收到响应,则把报错的信息作为这两种错误之一返回给进程
connect失败则该套接字不可再用,必须关闭,不能对这样的套接字再次调用connect函数。必须close后重新调用socket
将待连接套接字设置为监听套接字,并设置最大同时接收连接请求个数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型:
int listen(int sockfd, int backlog);
参数:
sockfd:待连接套接字
backlog:最大同时接收连接请求个数
返回值:
成功:0,并将 sockfd 设置为监听套接字
失败:-1
等待对端连接请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:监听套接字描述符
addr:已连接的对端客户的套接字地址结构
addrlen:调用时指示内核能向cliaddr写入的最大字节数,返回时内核指明实际写入的字节数
返回值:
成功:已连接套接字(非负整数)
失败:-1
accept用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠
如果对返回客户协议地址不感兴趣,可以把cliaddr和addrlen均置为空指针
数据的收发
功能:
从 TCP 套接字接收数据/发生数据
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:存储数据缓冲区
len:缓冲区大小
flags:接收标志
MSG_OOB:接收紧急(带外)数据
返回值:
成功:已接收字节数
失败:-1
备注:
当 flags 为 0 时,recv 与 read 作用相同。
用read、write进行数据收发也行
地址转换API
//把字符串形式“127.0.0.1”转化为网络能识别的格式
int inet_aton(const char* straddr,struct in_addr *addrp);
eg:
inet_aton(“192.168.124.109”,&c_addr.sin_addr);
``
//把网络格式的IP地址转化为字符串形式
char* inet_ntoa(struct in_addr sin_addr);
eg:
inet_ntoa(caddr.sin_addr);`
下面demo实现:server端可以接收连接,client端模仿客户接入,实现双方聊天通信,client可以随时退出,server端受到退出消息,继续等待
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/*struct sockaddr_in
{
u_short sin_family;// 地址族
u_short sin_port;// 端口
struct in_addr sin_addr;// IPV4 地址
char sin_zero[8];
};*/
int main()
{
int s_fd;
int c_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char *readbuf = NULL;
char *writebuf = NULL;
writebuf = malloc(1024*4);
readbuf = malloc(1024*4);
int n_read = 0;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8888);
inet_aton("192.168.20.131",&s_addr.sin_addr);
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
printf("socket error\n");
exit(-1);
}
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,5);
int c_len = sizeof(struct sockaddr_in);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len);
if(c_fd == -1){
perror("accept:");
exit(-1);
}
printf("client ip :%s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0)
{
if(fork() == 0){
while(1){
if(strcmp(readbuf,"quit") == 0){
memset(writebuf,0,1024);
write(c_fd,"quit",5);
printf("\nclient exit\n");
}
memset(writebuf,0,1024);
printf("\nServer Input:");
gets(writebuf);
write(c_fd,writebuf,strlen(writebuf));
}
}
while(1){
memset(readbuf,0,n_read);
n_read = read(c_fd,readbuf,1024);
printf("get client message:%s\n",readbuf);
if(strcmp(readbuf,"quit") == 0){
memset(writebuf,0,1024);
write(c_fd,"quit",5);
printf("\nclient exit\n");
exit(0);
}
}
}
}
return 0;
}
client.c
include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/*struct sockaddr_in
{
u_short sin_family;// 地址族
u_short sin_port;// 端口
struct in_addr sin_addr;// IPV4 地址
char sin_zero[8];
};*/
int main(int argc,char **argv)
{
int con;
int c_fd;
int n_read;
char *getbuf = NULL;
getbuf = malloc(1024*4);
char *senbuf = NULL;
senbuf = malloc(1024*4);
struct sockaddr_in c_addr;
c_addr.sin_family = AF_INET;
c_addr.sin_port =htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
printf("socket error\n");
exit(-1);
}
while(1){
con = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
if(con == -1){
perror("connect");
exit(-1);
}
if(fork() == 0){
if(fork() == 0){
while(1){
printf("\nClient Input:");
memset(senbuf,0,1024);
gets(senbuf);
write(c_fd,senbuf,strlen(senbuf));
}
}
}
while(1){
n_read = read(c_fd,getbuf,1024);
printf("get server message:%s\n",getbuf);
if(strcmp(getbuf,"quit") == 0){
printf("client exit!\n");
system("killall -9 client");
}
memset(getbuf,0,n_read);
}
}
return 0;
}
效果: