1 基本socket函数
Linux系统是通过提供套接字(socket)来进行网络编程的。网络的socket数据传输是一种特殊的I/O,socket也是一种文件描述符。socket也有一个类似于打
开文件的函数:socket(),调用socket(),该函数返回一个整型的socket的描述符,随后的连接建立、数据传输等操作也都是通过该socket实现。
1)socket函数
函数原型:
int socket(int domain, int type, int protocol);
功能说明:
调用成功,返回socket文件描述符;失败,返回-1,并设置errno
参数说明:
domain指明所使用的协议族,通常为PF_INET,表示TCP/IP协议;
type参数指定socket的类型,基本上有三种:数据流套接字、数据报套接字、原始套接字
protocol通常赋值"0"。
两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。socket数据结构中包含这五种信息。
2)bind函数
函数原型:
int bind(int sock_fd,struct sockaddr_in *my_addr, int addrlen);
功能说明:
将套接字和指定的端口相连。成功返回0,否则,返回-1,并置errno.
参数说明:
sock_fd是调用socket函数返回值,
my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;
struct sockaddr_in结构类型是用来保存socket信息的:
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
addrlen为sockaddr的长度。
3)connect函数
函数原型:
int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能说明:
客户端发送服务请求。成功返回0,否则返回-1,并置errno。
参数说明:
sock_fd 是socket函数返回的socket描述符;serv_addr是包含远端主机IP地址和端口号的指针;addrlen是结构sockaddr_in的长度。
4)listen函数
函数原型:
int listen(int sock_fd, int backlog);
功能说明:
等待指定的端口的出现客户端连接。调用成功返回0,否则,返回-1,并置errno.
参数说明:
sock_fd 是socket()函数返回值;
backlog指定在请求队列中允许的最大请求数
5)accecpt函数
函数原型:
int accept(int sock_fd, struct sockadd_in* addr, int addrlen);
功能说明:
用于接受客户端的服务请求,成功返回新的套接字描述符,失败返回-1,并置errno。
参数说明:
sock_fd是被监听的socket描述符,
addr通常是一个指向sockaddr_in变量的指针,
addrlen是结构sockaddr_in的长度。
6)write函数
函数原型:
ssize_t write(int fd,const void *buf,size_t nbytes)
功能说明:
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量.
在网络程序中,当我们向套接字文件描述符写时有俩种可能:
(1)write的返回值大于0,表示写了部分或者是全部的数据.
(2)返回的值小于0,此时出现了错误.需要根据错误类型来处理.
如果错误为EINTR表示在写的时候出现了中断错误.
如果错误为EPIPE表示网络连接出现了问题.
7)read函数
函数原型:
ssize_t read(int fd,void *buf,size_t nbyte)
函数说明:
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.
如果错误为EINTR说明读是由中断引起的,
如果错误是ECONNREST表示网络连接出了问题.
8)close函数
函数原型:
int close(sock_fd);
说明:
当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
函数运行成功返回0,否则返回-1
2 socket编程的其他函数说明
1) 网络字节顺序及其转换函数
(1) 网络字节顺序
每一台机器内部对变量的字节存储顺序不同,而网络传输的数据是一定要统一顺序的。所以对内部字节表示顺序与网络字节顺序不同的机器,一定要对数据进行转换,从程序的可移植性要求来讲,就算本机的内部字节表示顺序与网络字节顺序相同也应该在传输数据以前先调用数据转换函数,以便程序移植到其它机器上后能正确执行。真正转换还是不转换是由系统函数自己来决定的。
(2) 有关的转换函数
unsigned short int htons(unsigned short int hostshort):
//主机字节顺序转换成网络字节顺序,对无符号短型进行操作4bytes
unsigned long int htonl(unsigned long int hostlong):
//主机字节顺序转换成网络字节顺序,对无符号长型进行操作8bytes
unsigned short int ntohs(unsigned short int netshort):
//网络字节顺序转换成主机字节顺序,对无符号短型进行操作4bytes
unsigned long int ntohl(unsigned long int netlong):
//网络字节顺序转换成主机字节顺序,对无符号长型进行操作8bytes
注:以上函数原型定义在netinet/in.h里
2)IP地址转换
有三个函数将数字点形式表示的字符串IP地址与32位网络字节顺序的二进制形式的IP地址进行转换
(1) unsigned long int inet_addr(const char * cp):该函数把一个用数字和点表示的IP地址的字符串转换成一个无符号长整型,如:
struct sockaddr_in ina;
ina.sin_addr.s_addr=inet_addr("222.216.117.11");
该函数成功时:返回转换结果;失败时返回常量INADDR_NONE,该常量=-1,二进制的无符号整数-1相当于255.255.255.255,这是一个广播地址,所以在程序中调用iner_addr()时,一定要人为地对调用失败进行处理。由于该函数不能处理广播地址,所以在程序中应该使用函数inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函数将字符串形式的IP地址转换成二进制形式的IP地址;成功时返回1,否则返回0,转换后的IP地址存储在参数inp中。
(3) char * inet_ntoa(struct in-addr in):将32位二进制形式的ip地址转换为数字点形式的ip地址,结果在函数返回值中返回,返回的是一个指向字符串的指针。
3)字节处理函数
Socket地址是多字节数据,不是以空字符结尾的,这和C语言中的字符串是不同的。Linux提供了两组函数来处理多字节数据,一组以b(byte)开头,是和BSD系统兼容的函数,另一组以mem(内存)开头,是ANSI C提供的函数。
以b开头的函数有:
(1) void bzero(void * s,int n):将参数s指定的内存的前n个字节设置为0,通常它用来将套接字地址清0。
(2) void bcopy(const void * src,void * dest,int n):从参数src指定的内存区域拷贝指定数目的字节内容到参数dest指定的内存区域。
3 简单的客户端(client)和服务端(server)实现源码
1) 客户端源码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
// gcc -g -o simple_client simple_client.c
int main(int argc,char *argv[])
{
char ip[20]={"192.168.10.23"};
int port=10000;
int server_fd;
struct sockaddr_in server_listen_addr;
bzero(&server_listen_addr,sizeof(server_listen_addr));
server_listen_addr.sin_family=AF_INET;
server_listen_addr.sin_port=htons(port);
inet_pton(AF_INET,"192.168.10.23",(void*)&server_listen_addr.sin_addr);
// inet_aton(ip,&server_listen_addr.sin_addr);
// bzero(&(server_listen_addr.sin_zero),8);
server_fd=socket(AF_INET,SOCK_STREAM,0);
int ret=connect(server_fd,(const struct sockaddr *)&server_listen_addr,
sizeof(struct sockaddr));
printf("server_fd=[%d] ret=[%d]n",server_fd,ret);
if(ret<0)
{
perror("error: socket connect!");
exit(1);
}
char data[20]={"good luck!n"};
int num=send(server_fd,(void*)data,strlen(data),0);
printf("send bytes[%d][%s]n",num,data);
close(server_fd);
return 1;
}
2)服务端源码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
//gcc -g -o simple_server simple_server.c
int main(int argc,char *argv[])
{
int port=10000;
int server_listen_fd;
int server_accept_fd;
struct sockaddr_in server_listen_addr;
struct sockaddr_in server_accept_addr;
bzero(&server_listen_addr,sizeof(server_listen_addr));
bzero(&server_accept_addr,sizeof(server_accept_addr));
server_listen_addr.sin_family=AF_INET;
server_listen_addr.sin_addr.s_addr=INADDR_ANY;
server_listen_addr.sin_port=htons(port);
server_listen_fd=socket(AF_INET,SOCK_STREAM,0);
if(-1==server_listen_fd)
{
perror("fail to create socket!");
exit(1);
}
if(bind(server_listen_fd,(struct sockaddr*)&server_listen_addr,
sizeof(server_listen_addr))==-1)
{
perror("can't to bind");
exit(1);
}
if(listen(server_listen_fd,10)==-1)
{
perror("can't to bind");
exit(1);
}
while(1)
{
int size=sizeof(server_accept_addr);
printf("server socket begin accept:n");
server_accept_fd=accept(server_listen_fd,(struct sockaddr*)&server_accept_addr,&size);
printf("accept con_fd=%dn",con_fd);
if(server_accept_fd<0)
{
perror("error:socket accept!exited!n");
exit(1);
}
int num=0;
char data[200]={0};
int ret=read(server_accept_fd,(void*)data,199);
close(server_accept_fd);
printf("DATA:[%s]n",data);
}
return 1;
}
以上代码均在CentOS 5.4上编译调试通过。