Socket和基本结构体
Linux中的网络编程通过Socket(套接字)实现,Socket是一种文件描述符。
Socket有三种类型:
流式套接字(SOCK_STREAM):使用TCP协议。
数据报套接字(SOCK_DGRAM):使用UDP协议。
原始套接字(SOCK_RAW):使用IP协议,主要用于新的网络协议的测试等。
网络地址
在socket程序设计当中,struct sockaddr用于记录网络地址:
struct sockaddr
{
u_short sa_family;
char sa_data[14];
}
sa_family:协议族,采用“AF_XXX”的形式,如AF_INET(IP协议族)。
sa_data:14字节的特定协议地址。
地址结构
struct sockaddr_in
{
short int sa_family; /*协议族*/
unsigned short int sin_port; /*端口号*/
struct in_addr sin_addr; /*协议特定地址*/
unsigned char sin_zero[8]; /*填0*/
}
注意:在一般应用中,一般不会使用sockaddr,一般使用sockaddr_in,因为其操作简单。
typedef struct in_addr
{
union
{
struct
{
unsigned char s_b1,s_b2,s_b3,s_b4;
}S_un_b;
struct
{
unsigned short s_w1,s_w2;
}S_un_w;
unsigned long S_addr; /*通常在联合体当中使用这个结构体,此为32位无符号整数来记录IP地址(假设为IP协议)
}S_un;
}
地址转换
IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的IP地址是由32位的整数表示的,为了转换我们可以使用以下两个函数:
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr *in)
函数里面a代表ascii(字符),n代表network。
inet_aton是将a.b.c.d形式的IP转换为32位的IP,存储在inp指针里面。inet_ntoa是将32位IP地址转换为a.b.c.d的格式。
字节序转换
不同类型的CPU对变量的字节存储顺序可能不同:(低字节先传输为big endian),有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要统一的。所以当内部字节顺序和网络字节顺序不同是,必须要进行转换。
字节序转换函数
htons
把unsigned short 类型从主机序转换到网络序
htonl
把unsigned long类型从主机序转换到网络序
ntohs
把unsigned short 类型从网络序转换到主机序
ntohl
把unsigned long类型从网络序转换到主机序
IP与主机名:
在网络中标识一台主机可以用IP地址,也可以使用主机名。
struct hostent *gethostbyname(const char *hostname)
struct hostent
{
char *h_name; /*主机的正式名称*/
char *h_aliases; /*主机的别名*/
int h_addrtype; /*主机的地址类型 AF_INET*/
int h_length; /*主机的地址长度*/
char **h_addr_list; /*主机的IP地址列表*/
}
#define h_addr h_addr_list[0]; /*主机的第一个IP地址*/
基于TCP的网络编程
基于TCP-服务器
1 创建一个socket,用函数socket();
2 绑定IP地址、端口等信息到socket上,用函数bind();
3 设置最大的允许的连接数,用listen();
4 等待客户端的连接请求,用函数accept(),如果没有连接请求则程序阻塞在accept处。
5 收发数据,用函数send()和recv(),或者read()和write();
6 关闭网络连接;
基于TCP-客户端
1 创建一个socket,用函数socket();
2 设置要连接的服务器的IP地址和端口属性
3 连接服务器,用函数connect();
4 收发数据,用函数send()和recv(),或者read()和write();
5 关闭网络连接;
TCP通信实例如下:
1 TCP服务器程序:
/**********************************************************
*实验要求: 建立基于TCP协议的服务器与客户端的连接。客户端向服务器发送
* 字符串,服务器将收到的字符串打印出来。
*功能描述: 根据套接字建立TCP连接的过程,创建服务端程序,并在服务端等
* 待接收客户端的数据,并打印到终端上。
*日 期: 2010-9-17
*作 者: 国嵌
**********************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define portnumber 3333
/*
* 程序入口
* */
int main(int argc, char *argv[])
{
int sockfd,new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
int nbytes;
char buffer[1024];
/* 服务器端开始建立sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:IPV4;SOCK_STREAM:TCP
{
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* 服务器端填充 sockaddr结构 */
bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0
server_addr.sin_family=AF_INET; // Internet
server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // (将本机器上的long数据转化为网络上的long数据)服务器程序能运行在任何ip的主机上 //INADDR_ANY 表示主机可以是任意IP地址,即服务器程序可以绑定到所有的IP上
//server_addr.sin_addr.s_addr=inet_addr("192.168.1.1"); //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
/* 捆绑sockfd描述符到IP地址 */
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
/* 设置允许连接的最大客户端数 */
if(listen(sockfd,5)==-1)
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1)
{
/* 服务器阻塞,直到客户程序建立连接 */
sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
{
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 将网络地址转换成.字符串
if((nbytes=read(new_fd,buffer,1024))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes]='\0';
printf("Server received %s\n",buffer);
/* 这个通讯已经结束 */
close(new_fd);
/* 循环下一个 */
}
/* 结束通讯 */
close(sockfd);
exit(0);
}
2 TCP客户端程序:
/**********************************************************
*实验要求: 建立基于TCP协议的服务器与客户端的连接。客户端向服务器发送
* 字符串,服务器将收到的字符串打印出来。
*功能描述: 根据套接字建立TCP连接的过程,创建客户端程序,并在与服务端
* 建立连接后,向其发送指定字符串。
*日 期: 2010-9-17
*作 者: 国嵌
**********************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define portnumber 3333
/*
* 程序入口
* */
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
if(argc!=2)
{
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
exit(1);
}
/* 使用hostname查询host 名字 */
if((host=gethostbyname(argv[1]))==NULL)
{
fprintf(stderr,"Gethostname error\n");
exit(1);
}
/* 客户程序开始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:Internet;SOCK_STREAM:TCP
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/* 客户程序填充服务端的资料 */
bzero(&server_addr,sizeof(server_addr)); // 初始化,置0
server_addr.sin_family=AF_INET; // IPV4
server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
server_addr.sin_addr=*((struct in_addr *)host->h_addr); // IP地址
/* 客户程序发起连接请求 */
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/* 连接成功了 */
printf("Please input char:\n");
/* 发送数据 */
fgets(buffer,1024,stdin);
write(sockfd,buffer,strlen(buffer));
/* 结束通讯 */
close(sockfd);
exit(0);
}
运行结果:
先运行TCP服务器程序
[root@localhost TCP]# ./tcp_server
/****阻塞处*****/
发生阻塞,阻塞发生在tcp_server.c中的accept函数处
然后运行TCP客户端程序重新开一个终端:
[root@localhost TCP]# ./tcp_client 192.168.0.102 /*192.168.0.102为服务器地址*/
Please input char:
一旦连接后,主机端发生变化,如下:
[root@localhost TCP]# ./tcp_server
Server get connection from 192.168.0.102
/****阻塞处*****/
同时发生阻塞,阻塞发生在TCP_server.c中的read函数中,由于此时客户端没有向服务器发送数据
接下来我们从客户端发送数据:
[root@localhost TCP]# ./tcp_client 192.168.0.102
Please input char:
1234567
则服务器端也发生相应变化如下:
[root@localhost TCP]# ./tcp_server
Server get connection from 192.168.0.102
Server received 1234567
/*****阻塞处****/
又发生阻塞,阻塞在accept处,因为此时tcp_server.c中的while循环一次结束,关闭连接,又循环到accept处。依照此方法不断发送与接收数据。
此文章按照国嵌视频 国嵌视频\光盘3\课程2(嵌入式LINUX应用开发班)\第7天(网络编程)所撰写,此处予以感谢。