首先介绍两个用于处理网络通信中地址问题的结构体:sockaddr和sockaddr_in
sockaddr
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sa_family:是一个address family,也就是地址族;
sa_data:长度为14个字节,包含套接字(socket)中的目标地址和端口信息 。
sockaddr定义的头文件:sys/socket.h
sockaddr的缺陷是:把目标地址和端口信息混在同一个数组(sa_data)里
sockaddr_in
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
sockaddr_in解决了sockaddr 的缺陷,实际上相当于把sockaddr 中的sa_data分成了3个部分:
- sin_port 端口号
- sin_addr IP地址
- sin_zero 补齐空格
sockaddr_in 定义的头文件:netinet/in.h和arpa/inet.h
小结
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。也就是说,平时我们用的还是sockaddr_in,只是有些函数要求传入的参数是sockaddr,所以需要在传入时进行强制类型转换。
概括一下:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
实例介绍
本实例分为客户端(Client)和服务端(Server)两个部分。
服务端首先建立起socket,并与本地端口进行绑定,然后开始接收从客户端的连接请求并与该客户端的连接,接下来就可以接收客户端发送的消息了。
客户端在建立socket之后调用connect函数建立连接。
服务端的源代码如下:
//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <studio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321 //端口号
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main()
{
struct socketaddr_in server_sockaddr,client_sockaddr;
int sin_size,recvbytes;
int sockfd,client_fd;
char buf[BUFFER_SIZE];
if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM0)))
{
perror("socket");
exit(1);
}
printf("Socket id = %d\n",sockfd);
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
memset(server_sockaddr.sin_zero,0,8);
int i = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i));
//绑定函数bind
if( -1 == bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) )
{
perror("bind");
exit(1);
}
printf("Bind Success\n");
//listen函数
if( -1 == listen(sockfd,MAX_QUE_CONN_NM) )
{
perror("listen");
exit(1);
}
printf("listening...\n");
//accept函数
if( -1 == accept(sockfd,(struct sockaddr *)&server_sockaddr,&sin_size))
{
perror("accept");
exit(1);
}
//recv函数
memset(buf,0,sizeof(buf));
if( -1 == recv(client_fd,buf,BUFFER_SIZE,0))
{
perror(recv);
exit(1);
}
printf("Received a message: %s\n",buf);
close(fd);
exit(0);
}
客户端的源代码如下:
//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <studio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321 //端口号
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main(int argc, char *argv[])
{
int sockfd,sendbytes;
char buf[BUFFER_SIZE];
struct hostent *host;
struct sockaddr_in server_addr;
if(arg < 3)
{
fprintf(stderr,"USAGE: ./Client Hostname(or ip address) Text\n");
exit(1);
}
//地址解析函数
if( NULL == (host = gethostbyname(argv[1])))
{
perror("gethostbyname");
exit(1);
}
memset(buf,0,sizeof(buf));
sprintf(buf,"%s",argv[2]);
//创建socket
if( -1 == (sockfd = socket(AF_INET,SOCK_STREAM,0)) )
{
perror("socket");
exit(1);
}
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_addr.sin_addr = *( (struct in_addr*)host->h_addr );
memset(server_addr.sin_zero,0,8);
if( -1 == (connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr))) )
{
perror("connect");
exit(1);
}
//发送消息给服务器端
if( -1 == (sendbytes = send(sockfd,buf,strlen(buf),0)) )
{
perror("send");
exit(1);
}
close(sockfd);
exit(0);
}
使用方法
运行时需要先启动服务器,再启动客户端。
在Ubuntu中可以这样使用:
- 打开一个shell,并执行命令
./server
- 保持上一个shell打开,并打开第二个shell,执行命令
./client localhost Hello
注意事项
在client.c文件中不要忘记添加netdb.h头文件
netdb.h头文件里定义了与网络有关的结构、变量类型、宏、函数等。
/*通过IP地址获得主机有关的网络信息*/
struct hostent* gethostbyaddr(const void *addr, size_t len, int type);
/*通过主机名获得主机的网络信息*/
struct hostent* gethostbyname(const char *name);
因为我们在client.c中用到了gethostbyname函数,所以需要添加这个头文件。