网络编程就是编写程序使两台连网的计算机相互交换数据。而socket就是连接两台计算机的纽带,首先了解一些socket相关函数。
创建套接字所用的socket函数
#include
int socket (int domain,int type,int protocol);
成功时候返回socket文件描述符,失败时返回-1
domin是指套接字中使用的协议簇信息,如PF_INET/AF_INET(IPV4互联网协议簇),PF_INET6/AF_INET6(IPV6互联网协议簇)
type指套接字数据传输类型信息,有面向连接的套接字(SOCK_STREAM)和面向消息的套接字(SOCK_DGRAM),面向连接的套接字可以理解成TCP协议,特点有:传输过程中数据不会消失,按序传输数据,传输的数据不存在数据边界(意思就是接收数据和传输数据次数可以不同),因为收发数据的套接字内部有缓冲,因此服务器和客户端调用的read和write并不马上调用,有可能分多次调用。面向消息的套接字可以当作udp,特点就是快速传输,传输数据可能丢失,有数据边界,限制每次传输数据大小。
protocol指计算机间通信中使用的协议信息。一般都可以为0,如果同一协议簇中存在多个数据传输方式相同的协议,则才用第三个参数。
创建完套接字后就要描述socket一些特征。bind函数作用是把一个本地协议赋予一个套接字。如果一个TCP客户或者服务器不用bind绑定一个端口,则调用connect或listen时,内核就要为相应的套接字选择一个临时端口(一般TCP客户端可这样做,TCP服务器不可,因为服务器的端口是大家熟知的)
对于TCP客户端而言,通常不把IP地址绑定到套接字上,当连接套接字时(connect),内核将根据所用外出网络接口选择源IP地址。如果让内核来为套接字选择一个临时端口,那么需要用函数getsockname来返回协议地址
对于TCP服务器而言,如果套接字没有捆绑IP地址,内个就会把客户发送的SYN目的IP地址作为服务器的源IP地址。IPV4的INADDR_ANY就是通配地址
#include
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen)
//注意,绑定的是自己的地址myaddr(服务器的地址)
//成功时返回0,失败时返回1
sockfd是socket文件描述符,结构体sockaddr_in如下,最后一个参数就是地址长度。
struct sockaddr_in
{
sa_family_t sin_family; //地址簇,取值AF_INET(IPV4),AF_INET6(IPV6)
unit16_t sin_port; //16位TCP/UDP端口号,以网络字节序保存
struct in_addr sin_addr; //32位IP地址,以网络字节序保存
char sin_zero[8]; //不使用,但一般初始化为0
}
struct in_addr
{
In_addr_t s_addr; //32位IP地址
}
因为不同CPU内存保存数据方式有两种,分别是大端序(高位字节在低位地址,低位字节在高位地址,因为规定在传输数据中用大端序,所以也称网络字节序)和小端序(高位字节在高位地址,低位字节在低位地址,现代PC中大多采用小端字节序,所以也称为主机字节序),在网络传输数据时为了统一方式,约定用网络字节序(大端序),以下几个函数可以实现。
//short是16位,一般用于port,long一般用于address
unsigned short htons(unsigned short); //host to network把short类型数据
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
//还有两个可以把字符串形式的IP地址转换成32位整数型数据,成功调用后返回32位大端整数
in_addr_t inet_addr(const char * string);
int inet_aton(const char * string, struct in_addr * addr);
//iner_aton可以将转换后的IP地址信息带入sockaddr_in结构体中声明in_addr结构体变量。
char * inet_ntoa(struct in_addr adr);将网络字节序整数型IP地址转换成字符串
//以下两个函数ipv4和ipv6通用,p代表表达地址的表达格式通常是ASCII字符,n代表数值,数值格式是存放在套接字地址结构里面的二进制值
//第一个函数将strptr指针所指的字符串转换到addrptr指针存放二进制结果
int inet_pton(int family, const char *strptr, void *addrptr);
//这个函数将数值格式addrptr转换成表达格式strptr,若成功则指向结果的指针
const char *inet_ntop(int family,const void *addrptr, char *strptr, size_t len);
调用listen函数进入等待连接请求状态,仅用于TCP服务器用,他做两件事情
#include
int listen (int sock, int backlog); //sock位希望进入等待连接请求状态的套接字文字描述
//符,backlog为连接请求等待队列长度
用于TCP服务器调用,接收客户端连接请求,从已完成连接队列队头返回下一个已完成连接,函数返回的已连接套接字是自动创建的,并自动与发起连接请求的客户端建立连接。如果accept返回成功,那么其返回值是由内核自动生成的已连接套接字(称第一个由socket创建的参数为监听套接字)。一个服务器通常吃创建一个监听套接字,其在服务器的生命周期中一直存在,而内核为每个由服务器进程接受的客户连接创建一个已连接套接字,当服务器完成对客户的服务时,相应的已连接套接字即可关闭。
#include
int accept(int sock,struct sockaddr *cliaddr, socklen_t *addrlen);
//参数cliaddr和addrlen用来返回已连接的对端进程的协议地址
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
对于客户端的套接字比较简单,首先也是调用socket函数创建套接字,然后直接调用connect函数向服务器端发送连接请求。
connect函数用来建立TCP客户端与服务器的连接。如果是TCP套接字,调用connect函数将激发TCP三次握手过程,有以下三种出错情况。如出现调用失败,则需要close当前套接字描述符并重新调用socket
根据TCP状态转移,connect函数导致当前套接字从CLOSED状态变成SYN_SENT状态,若成功则再次转移到ESTABLISHED。参考:TCP详解
#include
//第二个和第三个参数分别是一个指向套接字地址结构的指针和该结构的大小
int connect (int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
------------------------------------------------------------------------------------------------------------------------------------------------------
客户端:
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc!=3)
{
printf("Usage:%s\n",argv[0]);
exit(1);
}
sock=socket(PF_INET,SOCK_STREAM,0);
if(sock==-1) printf("socket error");
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
printf("connect error");
str_len=read(sock,message,sizeof(message)-1);
if(str_len==-1) printf("read error");
printf("Message from server %s\n",message);
close(sock);
return 0;
}
服务器:
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[]="HELLO WORLD";
if(argc!=2)
{
printf("Usage:%s\n",argv[0]);
exit(1);
}
serv_sock=socket(PF_INET,SOCK_STREAM,0);
if(serv_sock==-1)
printf("socket error");
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
printf("bind error");
if(listen(serv_sock,5)==-1)
printf("listen error");
clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);
if(clnt_sock==-1)
printf("accept error");
write(clnt_sock,message,sizeof(message));
close(clnt_sock);close(serv_sock);
return 0;
}
参考:《TCP/IP网络编程》《UNP》《Linux高性能服务器编程》