操作系统有五大功能,简单总结为:
操作系统的五大功能:
1、进程管理
2、内存管理
3、文件系统
4、网络管理
5、设备管理
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
一、UNIX网络编程
在socket编程中会遇到很多函数,比如socket()、bind()、listen()等等,我会边学边记录熟悉这些函数的用法,简略的概述都在注释里,方便以后翻阅和学习
二、服务器端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MYPORT 8887
#define QUEUE 20
#define BUFFER_SIZE 1024
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); //定义服务器端socket函数
///定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);//需要监听的端口,这里选择ANY表示所有网络端口都监听
/*
http://blog.csdn.net/yaxiya/article/details/6722083
htonl就是把本机字节顺序转化为网络字节顺序
h---host 本地主机
to 就是to 了
n ---net 网络的意思
l 是 unsigned long
同理可得ntohl就是网络字节序转化为本机字节序
htonl和ntohl主要是为了防止在不同平台的机器数据存放格式不同
http://bbs.csdn.net/topics/80351112 论坛讨论ntohl和htonl
*/
/*
bind,成功返回0,出错返回-1
int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)
当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),
bind函数可以将一组固定的地址绑定到sockfd上。
其中:
sockfd是socket函数返回的描述符;
myaddr指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;
addrlen是前面struct sockaddr(与sockaddr_in等价)的长度。
*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
perror("bind");
exit(1);
}
/*listen,成功返回0,出错返回-1
int listen(int sockfd, int backlog);
开始监听套接字
sockfd:套接字,成功返回后进入监听模式,当有新连接并accept后会再建立一个套接字保存新的连接;
backlog:暂且翻译为后备连接吧!下面详细介绍此参数:
1) 当TCP接收一个连接后(三次握手通过)会将此连接存在连接请求队列里面,并对队列个数+1,而backlog为此队列允许的最大个数,超过此值,则直接将新的连接删除,即不在接收新的连接。将这些处于请求队列里面的连接暂记为后备连接,这些都在底层自动完成,底层将连接添加到队列后等待上层来处理(一般是调用accept函数接收连接);
2) 当上层调用accept函数接收一个连接(处于请求队列里面的后备连接),队列个数会-1;
3) 那么这样一个加一个减,只要底层提交的速度小于上层接收的速度(一般是这样),很明显backlog就不能限制连接的个数,只能限制后备连接的个数。那为啥要用这个backlog呢?主要用于并发处理,当上层没来的及接收时,底层可以提交多个连接;
4) backlog的取值范围 ,一般为0-5。
http://www.cnblogs.com/mddblog/p/4492784.html
*/
if(listen(server_sockfd,QUEUE) == -1) //开始监听
{
perror("listen");
exit(1);
}
///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
/*成功返回非负描述字,出错返回-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
开始等待客户端连过来套接字
addr 和 addrlen 一般是NULL, 否则只允许addr指定的客户端连过来。
*/
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if(conn<0)
{
perror("connect");
exit(1);
}
while(1)
{
memset(buffer,0,sizeof(buffer));//清空buffer
int len = recv(conn, buffer, sizeof(buffer),0);//recv函数返回其实际copy的字节数,http://blog.csdn.net/tiandyoin/article/details/30044781
if(strcmp(buffer,"exit\n")==0)
break;
fputs(buffer, stdout);//把接收到的内容输出到屏幕上
send(conn, buffer, len, 0);
}
close(conn);
close(server_sockfd);
return 0;
}
三、客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MYPORT 8887
#define BUFFER_SIZE 1024
int main()
{
int sock_cli = socket(AF_INET,SOCK_STREAM, 0);//定义socket客户端函数
/* int socket(int domain, int type, int protocol);
创建socket, 返回文件描述符
domain: AF_INET、 AF_INET6、 AF_UNIX、 AF_UPSPEC 指明了协议族/域比如AF_INET就是协议版本4,AF_INET6就是协议版本6
type: SOCK_DGRAM、 SOCK_RAW、 SOCK_SEQPACKET、 SOCK_STREAM type是套接口类型
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP。
protocol: IPPROTO_IP、 IPPROTO_IPV6、 IPPROTO_ICMP、 IPPROTO_RAW、
IPPROTO_TCP、 IPPROTO_UDP protocol:套接口所用的协议。如调用者不想指定,可用0指定,表示缺省
*/
//定义sockaddr_in结构体 http://blog.csdn.net/renchunlin66/article/details/52351751
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr)); //memset函数在驱动里见到过,清空某一段空间
servaddr.sin_family = AF_INET; //sin_family指代协议族,在socket编程中只能是AF_INET
servaddr.sin_port = htons(MYPORT); //服务器端口,避免冲突建议设置大一些,因为比较小的端口都是默认的
servaddr.sin_addr.s_addr = inet_addr("192.168.216.128"); //服务器ip
//连接服务器,成功返回0,错误返回-1
if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
/*
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
连接指定的服务器, 其中addr指定连接服务器的IP和端口
*/
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
/*
send类似于write
int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)
失败时返回 -1/SOCKET_ERROR
其中:
sockfd:发送端套接字描述符(非监听描述符)
buf:应用要发送数据的缓存
len:实际要发送的数据长度
flag:一般设置为0
recv类似于read
ssize_t recv(int sockfd,void *buf, size_t len,int flags)
其中:
sockfd:接收端套接字描述符;
buf:指定缓冲区地址,用于存储接收数据;
len:指定的用于接收数据的缓冲区长度;
flags:一般指定为0
其实在一般情况下可以互换,便于理解也可使用read和write
*/
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)//fgets函数在之前获取IP的时候用到过,相当于检测是否clint有写入吧。
{
send(sock_cli, sendbuf, strlen(sendbuf),0); //客户端发送
if(strcmp(sendbuf,"exit\n")==0)//在蜂鸣器中有用到strcmp,用来比较两个字符串,若完全相同则返回0
break;
recv(sock_cli, recvbuf, sizeof(recvbuf),0); //接收
fputs(recvbuf, stdout);//fputs函数向指定的文件指针中写入字符串,这里是向屏幕中输出接收到的信息
/*
stdout(Standardoutput)标准输出
stdin(Standardinput)标准输入
stderr(Standarderror)标准错误
*/
memset(sendbuf, 0, sizeof(sendbuf));//清空使用的空间
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sock_cli);//关闭socket
return 0;
}
四、客户–服务器通信
服务器端接收:
客户端发送:
要先打开服务器端,然后再打开客户端连接
//http://blog.csdn.net/g_brightboy/article/details/12854117常用socket函数