Unix环境下的Socket编程
来源:互联网 发布:帝国cms好吗 编辑:程序博客网 时间:2024/05/13 02:00
1、什么是 Socket?
Socket接口是TCP/IP 网络的 API,Socket 接口定义了许多函数或例程,程序员可以用它们来开发 TCP/IP 网络上的应用程序。要学 Internet 上的 TCP/IP 网络编程,必须理解 Socket 接口。
Socket接口设计者最先是将接口放在 Unix 操作系统里面的。如果了解 Unix 系统的输入和输出的话,就很容易了解 Socket 了。网络的 Socket 数据传输是一种特殊的 I/O , Socket 也是一种文件描述符 。 Socket也具有一个类似于打开文件的函数调用 Socket() ,该函数返回一个整型的 Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。常用的 Socket 类型有两种:
流式 Socket( SOCK_STREAM )和数据报式 Socket ( SOCK_DGRAM )。
流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;
数据报式 Socket是一种无连接的 Socket ,对应于无连接的 UDP 服务应用。
2、 Socket建立
为了建立 Socket,程序可以调用 Socket 函数,该函数返回一个类似于文件描述符的句柄。
socket函数原型为: int socket(int domain, int type, int protocol);
domain指明所使用的协议族,通常为 AF_INET ,表示互联网协议族( TCP/IP 协议族); type 参数指定 socket 的类型: SOCK_STREAM 或 SOCK_DGRAM , Socket 接口还定义了原始 Socket ( SOCK_RAW ),允许程序使用低层协议;
protocol通常赋值 "0" 。
Socket()调用返回一个整型 socket 描述符,你可以在后面的调用使用它 。
Socket描述符是一个指向内部数据结构的指针 ,它指向描述符表入口。调用 Socket函数时,socket执行体将建立一个 Socket ,实际上 " 建立一个 Socket" 意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。
两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。 Socket 数据结构中包含这五种信息。
3、 Socket配置
通过 socket调用返回一个 socket 描述符后,在使用 socket 进行网络传输以前,必须配置该 socket.
面向连接 的 socket客户端 通过调用 Connect函数在 socket 数据结构中保存本地和远端信息。(ps:这时候客户端这边不需要bind,服务器端需要bind)
无连接socket的客户端和服务端以及面向连接 socket 的服务端通过调用bind函数来配置本地信息。Bind函数将 socket 与本机上的一个端口相关联,随后你就可以在该端口监听服务请求。
Bind函数原型:
int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
Sockfd是调用 socket 函数返回的 socket 描述符 ,my_addr 是一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针; addrlen 常被设置为 sizeof(struct sockaddr) 。
struct sockaddr结构类型是用来保存 socket 信息的:
struct sockaddr {
unsigned short sa_family; /*地址族, AF_xxx */
char sa_data[14]; /* 14字节的协议地址 */
};
sa_family一般为 AF_INET ,代表 Internet ( TCP/IP )地址族;sa_data则包含该 socket 的 IP 地址和端口号。
另外还有一种结构类型 :
struct sockaddr_in {
short int sin_family; /*地址族 */
unsigned short int sin_port; /*端口号 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /*填充 0 以保持与struct sockaddr 同样大小 */
};
这个结构更方便使用。 sin_zero用来将 sockaddr_in 结构填充到与 struct sockaddr 同样的长度,可以用 bzero() 或 memset() 函数将其置为零。指向 sockaddr_in 的指针和指向 sockaddr 的指针可以相互转换,这意味着如果一个函数所需参数类型是 sockaddr 时,你可以在函数调用的时候将一个指向 sockaddr_in 的指针转换为指向 sockaddr 的指针;或者相反。
使用 bind函数时,可以用下面的赋值实现自动获得本机 IP 地址和随机获取一个没有被占用的端口号:
my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */
my_addr.sin_addr.s_addr =INADDR_ANY; /* 填入本机IP地址 */
通过将 my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。
同样,通过将 my_addr.sin_addr.s_addr置为 INADDR_ANY ,系统会自动填入本机 IP 地址。注意在使用 bind 函数是需要将 sin_port 和 sin_addr 转换成为网络字节优先顺序 ;而 sin_addr则不需要转换。
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输 ,所以对于在内部是以低位字节优先方式存储数据的机器,在 Internet上传输数据时就需要进行转换,否则就会出现数据不一致。
下面是几个字节顺序转换函数:
·htonl():把 32 位值从主机字节序转换成网络字节序
·htons():把 16 位值从主机字节序转换成网络字节序
·ntohl():把 32 位值从网络字节序转换成主机字节序
·ntohs():把 16 位值从网络字节序转换成主机字节序
Bind()函数在成功被调用时返回 0 ;出现错误时返回 "-1" 并将 errno 置为相应的错误号。
需要注意的是,在调用 bind函数时一般不要将端口号置为小于1024的值 ,因为1到1024是保留端口号 ,你可以选择大于 1024中的任何一个没有被占用的端口号。
4、连接建立
面向连接的客户程序使用 Connect函数来配置 socket 并与远端服务器建立一个 TCP 连接,其函数原型为:
int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd是 socket 函数返回的 socket 描述符;
serv_addr是包含远端主机 IP 地址和端口号的指针;
addrlen是远端地址结构的长度。
Connect函数在出现错误时返回 -1 ,并且设置 errno 为相应的错误码。
进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP 地址 ,而客户通过哪个端口与服务器建立连接并不需要关心, socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到打断口。
Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连 。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接,它只是被动的在协议端口监听客户的请求。
Listen函数使 socket 处于被动的监听模式,并为该 socket 建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
int listen(int sockfd, int backlog);
Sockfd是 Socket 系统调用返回的 socket 描述符;
backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待 accept() 它们(参考下文)。 Backlog 对队列中等待服务的请求的数目进行了限制,大多数系统缺省值为 20 。如果一个服务请求到来时,输入队列已满,该 socket 将拒绝连接请求,客户将收到一个出错信息。当出现错误时 listen函数返回 -1 ,并置相应的 errno 错误码。
accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用 accept 函数,然后睡眠并等待客户的连接请求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd是被监听的 socket 描述符, addr 通常是一个指向 sockaddr_in 变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求); addrlen 通常为一个指向值为 sizeof(struct sockaddr_in) 的整型指针变量。出现错误时 accept 函数返回 -1 并置相应的 errno 值。
首先,当 accept函数监视的 socket 收到连接请求时, socket 执行体将建立一个新的 socket ,执行体将这个新 socket 和请求连接进程的地址联系起来,收到服务请求的初始 socket 仍可以继续在以前的 socket 上监听,同时可以在新的 socket 描述符上进行数据传输操作。
5、数据传输
send()和recv()这两个函数用于面向连接 的 socket 上进行数据传输。
send()函数原型为:
int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用来传输数据的 socket 描述符;
msg是一个指向要发送数据的指针;
Len是以字节为单位的数据的长度;
flags一般情况下置为 0 (关于该参数的用法可参照 man 手册)。
Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。在程序中应该将 send() 的返回值与欲发送的字节数进行比较。当 send() 返回值与 len 不匹配时,应该对这种情况进行处理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
recv()函数原型为:
int recv(int sockfd,void *buf,int len,unsigned int flags);
Sockfd是接受数据的 socket 描述符;
buf是存放接收数据的缓冲区;
len是缓冲的长度。
Flags也被置为 0 。
Recv()返回实际上接收的字节数,当出现错误时,返回 -1 并置相应的 errno 值。
sendto()和recvfrom()用于在无连接的数据报 socket 方式下进行数据传输。由于本地 socket 并没有与远端机器建立连接,所以在发送数据时应指明目的地址。
sendto()函数原型为:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,
const struct sockaddr *to, int tolen);
该函数比 send()函数多了两个参数, to 表示目地机的 IP 地址和端口号信息,而 tolen 常常被赋值为 sizeof (struct sockaddr) 。 sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回 -1 。
recvfrom()函数原型为:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一个 struct sockaddr 类型的变量,该变量保存源机的 IP 地址及端口号。
fromlen常置 sizeof (struct sockaddr) 。当 recvfrom() 返回时, fromlen 包含实际存入 from 中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的 errno 。
如果你对数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。
6、结束传输
当所有的数据操作结束以后,你可以调用 close()函数来释放该 socket ,从而停止在该 socket 上的任何数据操作: close(sockfd);
你也可以调用 shutdown()函数来关闭该 socket 。该函数允许你只停止 在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某 socket的写操作而允许继续在该 socket 上接受数据,直至读入所有数据。
int shutdown(int sockfd,int how);
Sockfd是需要关闭的 socket 的描述符。参数 how 允许为 shutdown 操作选择以下几种方式:
·0-------不允许继续接收数据
·1-------不允许继续发送数据
·2-------不允许继续发送和接收数据,
· 均为允许则调用 close ()
shutdown在操作成功时返回 0 ,在出现错误时返回 -1 并置相应 errno 。
7、面向连接的 Socket实例
代码实例中的服务器通过 socket连接客户端。
该服务器软件代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>//inet_aton...
#include<unistd.h>
intnet_server_init (conststruct sockaddr_in *addr, intaddr_len);
intmain ()
{
intsockfd;
intaddr_len;
structsockaddr_in server_addr;
intport;
port = 5003;
addr_len =sizeof(structsockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr= INADDR_ANY;//本机地址
server_addr.sin_port=htons(port);
//connect to client
sockfd = net_server_init(&server_addr, addr_len);
if(!(sockfd<0)){
printf"connect OK!/n");
}
else{
printf("connect error!/n");
}
close(sockfd);
return0;
}
intnet_server_init (conststruct sockaddr_in *addr, intaddr_len)
{
intsocket_fd;//socket descriptor
intoptval;
socket_fd =socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(socket_fd == -1) {
printf("Failed to init socket/n" );
return-1;
}
/*--Reuseip address and port.*/
optval = 1;
if(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
sizeof(optval)) < 0) {
printf("Failed to set address reuse./n" );
}
if(bind(socket_fd, (structsockaddr *)addr, addr_len) < 0) {
printf("Failed to bind socket: %s/n" );
return-1;
}
if(listen(socket_fd, BACKLOG) < 0) {
printf("Failed to listen socket: %s/n" );
return-1;
}
returnsocket_fd;
}
服务器的工作流程是这样的:首先调用 socket函数创建一个 Socket ,然后调用 bind 函数将其与本机地址以及一个本地端口号绑定,然后调用 listen 在相应的 socket 上监听,当 accpet 接收到一个连接服务请求时,将生成一个新的socket 。
客户端程序代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>//memcpy()..memset()..strlen()...
#include<sys/socket.h>
#include<arpa/inet.h>//inet_aton...
#include<unistd.h> //close()
intnet_client_init(constchar*ip,intport);
intmain ()
{
intsockfd;
//connect to server
sockfd = net_client_init("192.168.1.96", 5003);
if(!(sockfd<0)){
printf("connect OK!/n");
}
else{
printf("connect error!/n");
}
close(sockfd);
return 0;
}
intnet_client_ init (constchar *ip, intport)
{
intsocket_fd;
intaddr_len;
structsockaddr_in *client_addr;
client_addr=(structsockaddr_in *)malloc(sizeof(struct sockaddr_in));
memset(client_addr, 0,sizeof(structsockaddr_in));
addr_len =sizeof(structsockaddr_in);
client_addr->sin_family =AF_INET;
//convert stringip to 32 bit net serial ip
if(inet_aton(ip, &client_addr->sin_addr) == 0){
printf("failed to convertip address form./n" );
return-1;
}
client_addr->sin_port = htons(port);//2 byte host to net
socket_fd =socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(socket_fd == -1) {
printf("Failed toinit socket/n" );
return-1;
}
if(connect(socket_fd, (conststruct sockaddr *)client_addr, addr_len) < 0){
printf("Failed to connect/n" );
return-1;
}
free(client_addr);
client_addr = NULL;
returnsocket_fd;
}
在客户端和服务器端分别用gcc编译,然后先运行服务器端可执行程序,这时候服务器端处于监听状态,然后在运行客户端可执行程序,连接服务器。连接成功则分别输出“connect ok!”。
- Unix/Linux环境下的Socket编程
- Unix/Linux环境下的Socket编程
- Unix环境下的Socket编程(转)
- Unix/Linux环境下的Socket编程
- Unix环境下的Socket编程
- Unix环境下的Socket编程
- Unix环境下的Socket编程
- Socket学习笔记 unix环境下的
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- Linux 环境下的Socket 编程
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- Linux环境下的Socket编程
- android
- 装饰模式
- Palindrome Partitioning
- python多进程
- 【codechef】Yet Another Problem On Strings (找最优解)
- Unix环境下的Socket编程
- 二叉树的前序遍历、中序遍历和后序遍历及其算法
- linux下如何设置环境变量PATH
- 一款轻量级的项目管理工具Redmine
- ArcGIS制图——多图层道路压盖处理
- OpenCV获取与设置像素点的值的几个方法
- 阅读程序5、6
- android 进程查看
- 1.4.2工作原理章节