一、服务器
1、首先知道linux中网络地址的表示,有两个重要的数据类型: sockaddr和 sockaddr_in
struct sockaddr{
unsigned short sa_fmaily; //address族,AF_xxx
char sa_data[14];//14 bytes的协议地址
}
sa_fmaily中 AF_INET代表IPV4协议;sa_data中包含了一些远程电脑的地址,端口和套接字的数目
struct sockaddr_in{
short int sin_fmaily;//Internet地址族
unsigned short int sin_port; //端口号
struct int_addr sin_addr; //Internet地址
unsigned char sin_zero[8]; //添0,(和struct sockaddr一样大小)
}
使用sockaddr_in的时候要把sin_zero全部置为0
2、建立socket链接
包含头文件<sys/types.h> <sys/socket.h>
int socket(int family, int type , int protocol); family代表协议或地址族;type代表套接字的类型,对于TCP为 SOCK_STREAM,对于UDP为SOCK_DGRAM,原始套接字为SOCK_RAW;
protocol表示使用的协议号。用0制定默认的family和type的默认协议号
返回值:建立成功返回非负值,否则-1; 错误原因EPROTONOSUPPORT:表示申请的服务或制定的协议无效;EMFILE:应用程序的描述符表已经满了;NFILE:内部系统文件已满;ENOBUFS:系统没有可用的缓冲空间
3、绑定本地地址
一个很重要的函数bind(),可以制定一个套接字使用的地址和端口,socket建立的一个socketfd绑定上一个本地的地址和端口;当需要进行端口监听(listen)操作需要经过这一步;当已经有一个链接的时候就不要这一步;
头文件<sys/types.h> <sys/socket.h>
int bind(int sockfd, struct sockaddr *sa, int addrlen);
参数:sockfd,是由socket()函数返回的套接字描述符; sa,是一个指向struct sockaddr的指针,包含有关地址的有关信息,例如名称,端口,IP地址;
addrlen,是套接字地址接口的长度,可以设置为sizeof(struct sockaddr);
成功返回 0 ,否则-1,常见错误是EADDRINUSER,表示端口已经被占用
例如:struct sockaddr_in server_addr;
server_addr.sin_addr.s_addr=htonl(INADDRY_ANY);存储是需要绑定的IP地址,也可以制定一个特定的IP用下面的语句是:server_addr.sin_addr.s_addr=inet_addr("192.268.1.1");
server_addr.sin_port=htons(portnumber);存储要绑定的端口号。
4、字节顺序转换
htonl()和htons()函数作用是进行网络字节顺序的转换;“HOST to Network Short”主机字节顺序转换为网络字节顺序----HTONLS ; "HTONL"-----“Host to Network Short”是无符号整型操作
5、IP地址转换
其中一个陌生的函数:inet_addr(); 包含的头文件是<arpa/inet.h>;
server_addr.sin_addr.s_addr=inet_addr("192.168.1.1");
如果想把struct in_addr里存储的网络地址显示出来,可以使用函数inet_ntoa();
char *inet_ntoa(struct in_addr inaddr);这个函数可以将一个32为的网络字节顺序的struct in_addr结构体转换为相应的点分十进制字符串;
6、Listen函数
当调用bind()函数,将一个套接字绑定到某个端口之后,就可以通过Listen()函数来接受客户端提出的连接请求。
头文件<sys/socket.h>
int listen(int sockfd, int backlog); backlog是未经过处理的连接请求队列可以容纳的最大数目
7、等待连接
<sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
addr:一般是一个指向struct sockaddr_in 结构的指针,将在accept()函数调用返回后填入远程连接过来的计算机的信息,比如远程计算机的IP地址和端口
例如:new_fd=accept(sockfd, (struct sockaddr*)&client_addr,&sin_size);
8、数据通信
socket本质就是文件描述符,因此凡是基于文件描述符的I/O函数都可以用于数据通信。如read,write,put,get等
但是read,write函数并不是针对套接字而设计,虽然也能传送数据但是缺少对网络的控制选项。
<1>其中send(),recv()函数是最基本的
int send(int sockfd, const void*msg, int len, int flags);
int recv( int sockfd, void*buf, int len, unsigned int flags);
msg是一个指针,指向想发送或存储信息的地址
len是发送信息的长度, flags是发送或者接受的标记,一般都设置为0.
<2>是sendto()和recvfrom()函数
用于进行无连接的UDP通信时使用的。
int sendto(int sockfd,const void*msg, int len, unsigned int flags, const struct sockaddr*to , int tolen);
int recvfrom(int sockfd,void*buf,int len, unsigned int flags, struct sockaddr *from, int *fromlen);
9、关闭套接字close(new_fd);
二、客户端
1、DNS(Domain Name Service)域名服务,实际上IP地址都是很难记忆的,通常是借助DNS服务,有了它可以通过一个可读性非常强的因特网名字得到这个名字所代表的IP地址,转换为IP地址后可以使用标准的套接字函数。
struct hostent *gethostbyname(const char*name);
它返回了一个指向struct hostent的指针,
struct hostent{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}
h_name是这个主机的正式名称;h_aliases是一个以NULL结尾的数组,存储了主机的备用名称;h_addrtype是返回地址的类型一般来说是AF_INET; h_length是地址的字节长度; h_addr_list是一个以0结尾的数组,存储了主机的网络地址;h_addr是h_addr_list数组的第一个成员。
host=gethostbyname(argv[1]);
server_addr.sin_addr=*((struct in_addr*)host->h_addr);
其中h_addr是char**类型的,所以在赋值的时候还得做一个指针的类型转换;
2、连接服务器
<sys/types.h> <sys/socket.h>
int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);
参数serv_addr是一个存储远程计算机的IP地址和端口信息的sockaddr结构;
参数addrlen是serv_addr结构体所占的内存大小;
connect()函数调用成功返回0,错误返回-1;
例如客户端代码:这是一个函数,传递过来一个IP,可以用#define IP 192.168.1.111; buffer为一个带有数据的缓冲; BMPsize为这个buffer的有效数据长度。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
//#define MAXSIZE 1400*1200
int sendData(char *ip,void *buffer,long BMPsize){
int sockfd ,n;
//void *buffer=malloc(MAXSIZE);
printf("the ip is %s\n",ip);
printf("the BMPsize is %ld",BMPsize);
struct sockaddr_in servaddr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){
printf("create socket error!!\n");
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(6666);
if(inet_pton(AF_INET,ip,&servaddr.sin_addr)<=0){
printf("inet_pton error for %s!!\n",ip);
return -1;
}
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){
printf("connect error !!!!\n");
return -1;
}
printf("start to send data to servr:\n");
if(send(sockfd,buffer,BMPsize,0)<0){
printf("send data error!\n");
return -1;
}
close(sockfd);
return 0;
}
另一端我们可以用java或者C的socket编程来接收数据,我是用的一段Java代码如下:
package cn.edu;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class pic {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
FileOutputStream fos=null;
DataInputStream dis=null;
byte[] buffer =new byte[1024*1024];
ServerSocket serverSocket=new ServerSocket(6666);
Socket socket=serverSocket.accept();
System.out.println("connect success!\n");
dis=new DataInputStream(socket.getInputStream());
fos=new FileOutputStream(new File("D:/rec.jpg"));
int length=0;
while((length=dis.read(buffer,0,buffer.length))>0){
fos.write(buffer, 0, length);
fos.flush();
}
//int length=dis.read(buffer,0,buffer.length);
//fos.write(buffer, 0,length );
serverSocket.close();
dis.close();
fos.close();
}
}
这边我传递是图片数据,当然传递任何的文件都可以!!!